public-activity / public_activity

Easy activity tracking for models - similar to Github's Public Activity

Home Page:https://github.com/pokonski/public_activity

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Issue when tracking is triggered from within a thread

emm26 opened this issue · comments

Hi, I am hoping you can help me with this. Please let me know if you need any more information.

I am using v '1.6.4' of the gem.

I have this in my model:

class MyModel
  include PublicActivity::Model
    tracked only: :create,
            owner: proc { |controller, model| controller.current_user },
            organization: proc { |controller, model| controller.current_user.organization }

and I have an action in the controller that triggers the tracking by creating an instance of the model above inside a thread:

class MyController
  def my_action
    Thread.new { MyModel.new } # triggers the tracking of the activity
  end
end

The problem I am facing is that inside MyModel, more specifically inside this block:
organization: proc { |controller, model| controller.current_user.organization } is that controller is set to nil

I believe the issue is to do with threads. This is how PublicActivity.get_controller and PublicActivity.set_controller are defined in store_controller.rb:

module PublicActivity
  class << self
    # Setter for remembering controller instance
    def set_controller(controller)
      Thread.current[:public_activity_controller] = controller
    end

    # Getter for accessing the controller instance
    def get_controller
      Thread.current[:public_activity_controller]
    end
  end
...
end

I have created this monkey patch which seems to solve the issue for me:

module PublicActivity
  class << self
    # Getter for accessing the controller instance
    def get_controller
      Thread.current[:public_activity_controller] ||
        # the next line is the monkey patch itself: when the tracking process gets triggered
        # from inside a Thread, the newly created Thread does not contain :public_activity_controller
        # The solution here looks for a Thread that contains :public_activity_controller
        Thread.list.map { |thread| thread[:public_activity_controller] }.compact.first
    end
  end
end

My suspicion here is that the parent thread of the current thread contains :public_activity_controller but the current one, does not. Hope this makes sense

Do you foresee any alternative solutions?

EDIT: It would be better in the monkey patch above, to look :public_activity_controller in just the ancestors of the current thread. I was looking for ways of doing the former using built-in methods from the Thread class but could not find any

@emm26 Technically, what you’re asking for is a code review. I’m not quite sure this is the best place to ask as GH issues are a better fit for, well, issues as in bugs. However, this is not a bug in PublicActivity but in your code. Thus, I’ll try to provide some guidance. As you’ve asked code style related questions in other repos (e.g. Liquid) I still think you’d be better served asking that type of question on StackOverflow or a local meetup group. Anyway, let’s go:

Your issue stems from the following code:

Thread.new { MyModel.new } # triggers the tracking of the activity

Doing that, you effectively break some assumptions about regular requests, i.e. there’s just 1 thread (maybe even just 1 process if you use Unicorn as your app server). By using Thread.new { … } you deliberately break out of this pattern, so default assumptions about 1 thread/request no longer holds true. In other words, you’re now in the driving seat, so you have to initialize your objects manually and can no longer rely on PublicActvity to get a reference of the controller. In your particular example the solution seems to be rather trivial though as you have everything in scope you need:

Thread.new { MyModel.new(owner: current_user) }

That’s it. You can still leave the following code in your model(s):

tracked only: :create,
        owner: proc { |controller, model| controller.current_user },
        organization: proc { |controller, model| controller.current_user.organization }

Based on the above you might want to use the following though:

Thread.new { MyModel.new(owner: current_user, organization: current_user.organization) }

Effectively, you’re simply overwriting the default proc to dynamically setup owner/organization by being explicit.

I hope this helps; closing this issue as it’s not a bug in PublicActivity itself.

@ur5us Thank you for looking into it and your reply.

Is the following a rails convention/assumption or is it specific to this gem?

you effectively break some assumptions about regular requests, i.e. there’s just 1 thread (maybe even just 1 process if you use Unicorn as your app server)

I will keep in mind to ask in SO/other sources. I thought this was genuinely a bad generalisation in the gem’s code. The goal of attaching some code was to better exemplify the issue

Best,
Ed

@emm26 This is neither a PublicActivity nor a Ruby on Rails convention. All Ruby app servers (Puma, Unicorn, etc) work like that. Each request is handled by 1 thread/process by default. This is no different in other languages/frameworks. If you spin up extra threads then you have to manage that manually by passing in data explicitly which is what I did in my examples.