Getting the NoMethodError error when editing the topic due to my plugin

Hi I am getting following error when trying to update ( edit ) the post

Error Info

NoMethodError - undefined method `call' for nil:NilClass
Did you mean?  caller:
  lib/post_revisor.rb:335:in `block (2 levels) in update_topic'
  lib/post_revisor.rb:333:in `block in update_topic'
  activerecord (4.2.7.1) lib/active_record/connection_adapters/abstract/database_statements.rb:211:in `transaction'
  activerecord (4.2.7.1) lib/active_record/transactions.rb:220:in `transaction'
  lib/post_revisor.rb:332:in `update_topic'
  lib/post_revisor.rb:242:in `revise'
  lib/post_revisor.rb:204:in `revise_post'
  lib/post_revisor.rb:152:in `block in revise!'
  activerecord (4.2.7.1) lib/active_record/connection_adapters/abstract/database_statements.rb:213:in `block in transaction'
  activerecord (4.2.7.1) lib/active_record/connection_adapters/abstract/transaction.rb:184:in `within_new_transaction'
  activerecord (4.2.7.1) lib/active_record/connection_adapters/abstract/database_statements.rb:213:in `transaction'
  activerecord (4.2.7.1) lib/active_record/transactions.rb:220:in `transaction'
  lib/post_revisor.rb:151:in `revise!'
  app/controllers/topics_controller.rb:243:in `update'
  actionpack (4.2.7.1) lib/action_controller/metal/implicit_render.rb:4:in `send_action'
  actionpack (4.2.7.1) lib/abstract_controller/base.rb:198:in `process_action'
  actionpack (4.2.7.1) lib/action_controller/metal/rendering.rb:10:in `process_action'
  actionpack (4.2.7.1) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
  activesupport (4.2.7.1) lib/active_support/callbacks.rb:117:in `call'
  activesupport (4.2.7.1) lib/active_support/callbacks.rb:555:in `block (2 levels) in compile'
  activesupport (4.2.7.1) lib/active_support/callbacks.rb:505:in `call'
  activesupport (4.2.7.1) lib/active_support/callbacks.rb:92:in `__run_callbacks__'
  activesupport (4.2.7.1) lib/active_support/callbacks.rb:778:in `_run_process_action_callbacks'
  activesupport (4.2.7.1) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (4.2.7.1) lib/abstract_controller/callbacks.rb:19:in `process_action'
  actionpack (4.2.7.1) lib/action_controller/metal/rescue.rb:29:in `process_action'
  actionpack (4.2.7.1) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
  activesupport (4.2.7.1) lib/active_support/notifications.rb:164:in `block in instrument'
  activesupport (4.2.7.1) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
  activesupport (4.2.7.1) lib/active_support/notifications.rb:164:in `instrument'
  actionpack (4.2.7.1) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
  actionpack (4.2.7.1) lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
  activerecord (4.2.7.1) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
  actionpack (4.2.7.1) lib/abstract_controller/base.rb:137:in `process'
  actionview (4.2.7.1) lib/action_view/rendering.rb:30:in `process'
  rack-mini-profiler (0.10.1) lib/mini_profiler/profiling_methods.rb:102:in `block in profile_method'
  actionpack (4.2.7.1) lib/action_controller/metal.rb:196:in `dispatch'
  actionpack (4.2.7.1) lib/action_controller/metal/rack_delegation.rb:13:in `dispatch'
  actionpack (4.2.7.1) lib/action_controller/metal.rb:237:in `block in action'
  actionpack (4.2.7.1) lib/action_dispatch/routing/route_set.rb:74:in `dispatch'
  actionpack (4.2.7.1) lib/action_dispatch/routing/route_set.rb:43:in `serve'
  actionpack (4.2.7.1) lib/action_dispatch/journey/router.rb:43:in `block in serve'
  actionpack (4.2.7.1) lib/action_dispatch/journey/router.rb:30:in `serve'
  actionpack (4.2.7.1) lib/action_dispatch/routing/route_set.rb:817:in `call'
  rack-protection (1.5.3) lib/rack/protection/frame_options.rb:31:in `call'
  omniauth (1.3.1) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.3.1) lib/omniauth/strategy.rb:164:in `call'
  omniauth (1.3.1) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.3.1) lib/omniauth/strategy.rb:164:in `call'
  omniauth (1.3.1) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.3.1) lib/omniauth/strategy.rb:164:in `call'
  omniauth (1.3.1) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.3.1) lib/omniauth/strategy.rb:164:in `call'
  omniauth (1.3.1) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.3.1) lib/omniauth/strategy.rb:164:in `call'
  omniauth (1.3.1) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.3.1) lib/omniauth/strategy.rb:164:in `call'
  omniauth (1.3.1) lib/omniauth/builder.rb:63:in `call'
  rack (1.6.5) lib/rack/conditionalget.rb:38:in `call'
  rack (1.6.5) lib/rack/head.rb:13:in `call'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/flash.rb:260:in `call'
  rack (1.6.5) lib/rack/session/abstract/id.rb:225:in `context'
  rack (1.6.5) lib/rack/session/abstract/id.rb:220:in `call'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/cookies.rb:560:in `call'
  activerecord (4.2.7.1) lib/active_record/query_cache.rb:36:in `call'
  activerecord (4.2.7.1) lib/active_record/connection_adapters/abstract/connection_pool.rb:653:in `call'
  activerecord (4.2.7.1) lib/active_record/migration.rb:377:in `call'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
  activesupport (4.2.7.1) lib/active_support/callbacks.rb:88:in `__run_callbacks__'
  activesupport (4.2.7.1) lib/active_support/callbacks.rb:778:in `_run_call_callbacks'
  activesupport (4.2.7.1) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/reloader.rb:73:in `call'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/remote_ip.rb:78:in `call'
  better_errors (2.1.1) lib/better_errors/middleware.rb:84:in `protected_app_call'
  better_errors (2.1.1) lib/better_errors/middleware.rb:79:in `better_errors_call'
  better_errors (2.1.1) lib/better_errors/middleware.rb:57:in `call'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
  logster (1.2.7) lib/logster/middleware/reporter.rb:31:in `call'
  railties (4.2.7.1) lib/rails/rack/logger.rb:38:in `call_app'
  railties (4.2.7.1) lib/rails/rack/logger.rb:22:in `call'
  config/initializers/100-quiet_logger.rb:17:in `call_with_quiet_assets'
  config/initializers/100-silence_logger.rb:29:in `call'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/request_id.rb:21:in `call'
  rack (1.6.5) lib/rack/methodoverride.rb:22:in `call'
  rack (1.6.5) lib/rack/runtime.rb:18:in `call'
  actionpack (4.2.7.1) lib/action_dispatch/middleware/static.rb:120:in `call'
  rack (1.6.5) lib/rack/sendfile.rb:113:in `call'
  lib/middleware/missing_avatars.rb:21:in `call'
  lib/middleware/turbo_dev.rb:33:in `call'
  rack-mini-profiler (0.10.1) lib/mini_profiler/profiler.rb:278:in `call'
  message_bus (2.0.2) lib/message_bus/rack/middleware.rb:62:in `call'
  railties (4.2.7.1) lib/rails/engine.rb:518:in `call'
  railties (4.2.7.1) lib/rails/application.rb:165:in `call'
  railties (4.2.7.1) lib/rails/railtie.rb:194:in `method_missing'
  rack (1.6.5) lib/rack/urlmap.rb:66:in `block in call'
  rack (1.6.5) lib/rack/urlmap.rb:50:in `call'
  rack (1.6.5) lib/rack/content_length.rb:15:in `call'
  puma (3.6.0) lib/puma/configuration.rb:225:in `call'
  puma (3.6.0) lib/puma/server.rb:578:in `handle_request'
  puma (3.6.0) lib/puma/server.rb:415:in `process_client'
  puma (3.6.0) lib/puma/server.rb:275:in `block in run'
  puma (3.6.0) lib/puma/thread_pool.rb:116:in `block in spawn_thread'

When I tried running discourse in safe-mode, with no unofficial plugins, I didnt get the error

In my plugin, I am saving the custom field to the discourse topic when it is created
I am able to save save the custom field, but I am not able to figure out why it is throwing above error when editing the topic. Am I missing something, do I have to handle edit scenarios?

> I am also changing the category of the topic based on post, can this break the edit functionality?

Topic.register_custom_field_type('test', :text)
PostRevisor.track_topic_field(:testid) 
DiscourseEvent.on(:topic_created) do | topic, params, user |
    testid = params[:testid].to_s
    topic.custom_fields['test'] = testid;
    category_id = 1 # Set it to undefined category
   
    # I also change the category of the topic here, after finding the category based on the testid

    @category = Category.find_by(name: testid)
   
    if @category then  
      category_id = @category[:id];
      puts "category does exits"
    else
      puts "category does not exits"
      @category = Category.create({"name"=>testid, "color"=>"AB9364", "text_color"=>"FFFFFF", "parent_category_id"=>"", "slug"=>"", "allow_badges"=>"true", "sort_order"=>"", "topic_featured_link_allowed"=>"true", "default_view"=>"latest", "permissions"=>{"everyone"=>1}}.merge(user: user))
      if @category.save
        category_id = @category[:id];  
      end
    end
   topic[:category_id] = category_id;
  topic.save
end

add_to_serializer :topic_view_ , :test do
    object.topic.custom_fields['test']  
end

add_to_serializer :topic_list_item_ , :test do
    object.custom_fields['test']  
end

I was also going through discourse-tagging plugin, and here they are checking if the tags are present or not. Do I have to do something similar?

https://github.com/discourse/discourse-tagging/blob/master/plugin.rb#L388

track_topic_field is expecting a block to execute when the tracked field is changed, so the following line:

PostRevisor.track_topic_field(:testid)

Will break the thing, whereas

PostRevisor.track_topic_field(:testid) { |tc| puts "Hello world!" }

will work (and, I reckon, print ‘Hello world!’ to the console when you change the value of ‘testid’ on a topic)

I don’t get what the intended functionality is here, so can’t offer further advice, but if you’re tracking a topic field you also need to provide what to do once that field has changed.

6 Likes

EDIT

Thanks, I tried the above code , and it worked.
Thankyou

I was trying something on similar lines

Here they watch the topic_guuid

Then they get that value in before_create_topic event

I am able to save the custom model to topic, but when I try to edit the first post of topic, it throws error.
If I comment out the PostRevisor code, the error dosent come. So I think it is due to PostRevisor only.

I do not have to do anything if value changes, I just need to get the value from UI and use it in plugin.rb to save it to topic. And it throws only in when editing the first post of topic. In the postrevisor class, I kept breakpoint to see

def update_topic
    Topic.transaction do
      PostRevisor.tracked_topic_fields.each do |f, cb|

        if !@topic_changes.errored? && @fields.has_key?(f)
          cb.call(@topic_changes, @fields[f])
        end
      end

      unless @topic_changes.errored?
        @topic_changes.check_result(@topic.save(validate: @validate_topic))
      end
    end
  end

Here in the PostRevisor.tracked_topic_fields array, the value for testid is “nil”.
Here is the snippet. When it comes to testid, it throws error

{:title=>#<Proc:0x007f8e5e3f9258@/Users/santosh/discourse/lib/post_revisor.rb:65>, :category_id=>#<Proc:0x007f8e5ad8b130@/Users/santosh/discourse/lib/post_revisor.rb:70>, :tags=>#<Proc:0x007f8e5ad8a438@/Users/santosh/discourse/lib/post_revisor.rb:75>, :tags_empty_array=>#<Proc:0x007f8e5ad8a348@/Users/santosh/discourse/lib/post_revisor.rb:87>, :featured_link=>#<Proc:0x007f8e5ad895b0@/Users/santosh/discourse/lib/post_revisor.rb:98>, :testid=>nil}

I’m sensing a misunderstanding here. The definition for PostRevisor.track_topic_field is essentially

def self.track_topic_field(field, &block)
  tracked_topic_fields[field] = block # store this block for later
end

Then, later on once the topic has changed:

# key here is the name of the field, and value is the block we stored
# (key, value is called f, cb in the code; we're iterating over a Hash)
tracked_topic_fields.each do |key, value|
  value.call() # call the block we passed earlier. If this is nil, it will blow up
end

So… if you don’t pass a block to track_topic_field, it’s going to go badly when running that update_topic code, because it’s expecting a block (technically a Proc, which is an object that always responds to the call method) to execute as an update callback.

4 Likes

Thankyou for detailed explanation
You are correct, passing the block ( even something like logging hello world ), didnt throw the error when updating the topic.