I have a standard Rails application.
When a Tip is created, I would like to create a Message for each User who is interested in that Tip.
This sounds simple right? It should be...
So, we start with a Tip Observer:
class TipObserver < ActiveRecord::Observer
def after_save(tip)
# after the tip is saved, we'll create some messages to inform the users
users = User.interested_in(tip) # get the relevant users
users.each do |u|
m = Message.new
m.recipient = u
link_to_tip = tip_path(tip)
m.body = "Hello #{u.name}, a new tip: #{link_to_tip}"
m.save!
end
end
end
Errors:
tip_observer.rb:13:in `after_save': undefined method `tip_path' for #<TipObserver:0xb75ca17c> (NoMethodError)
Ok, so TipObserver needs access to the UrlWriter methods. This should be fairly straightforward to fix, right?
class TipObserver < ActiveRecord::Observer
include ActionController::UrlWriter
Now it runs(!) and Outputs:
Hello dave18, a new tip: /tips/511
Great that works!! Well it kinda, really we want that to be a click-able link. Again, that should be easy right?
link_to_tip = link_to tip.name, tip_path(tip)
Errors:
tip_observer.rb:13:in `after_save': undefined method `link_to' for #<TipObserver:0xb75f7708> (NoMethodError)
Ok, so this time TipObserver needs access to the UrlHelper methods. This should be fairly straightforward to fix, right?
class TipObserver < ActiveRecord::Observer
include ActionController::UrlWriter
include ActionView::Helpers::UrlHelper
Errors:
whiny_nil.rb:52:in `method_missing': undefined method `url_for' for nil:NilClass (NoMethodError)
Ok, it seems adding that has interfered with the url_for declaration. Lets try the includes in a different order:
class TipObserver < ActiveRecord::Observer
include ActionView::Helpers::UrlHelper
include ActionController::UrlWriter
Errors:
url_rewriter.rb:127:in `merge': can't convert String into Hash (TypeError)
Hmm, there's no obvious way around this. But after reading some clever-clogs suggestion that Sweepers are the same as Observers but have access to the url helpers. So lets convert the Observer to a Sweeper and remove the UrlHelper and UrlWriter.
class TipObserver < ActionController::Caching::Sweeper
observe Tip
#include ActionView::Helpers::UrlHelper
#include ActionController::UrlWriter
Well, that allows it to run, but here's the Output:
Hello torey39, a new tip:
So, there's no error, but the url is not generated. Further investigation with the console reveals that:
tip_path => nil
and therefore:
tip_path(tip) => nil
Ok well I have no idea how to fix that problem, so perhaps we can attack this from a different direction. If we move the content into an erb template, and render the Message.body as a view - that gives two benefits - firstly the "View" content is put in the correct location, and it might help us avoid these *_path problems.
So lets change the after_save method:
def after_save(tip)
...
template_instance = ActionView::Base.new(Rails::Configuration.new.view_path)
m.body = template_instance.render(:partial => "messages/tip", :locals => {
:user=>user,
:tip=>tip
})
m.save!
end
Errors:
undefined method `url_for' for nil:NilClass (ActionView::TemplateError)
Great, but now we're back to this bloody url_for again. So this time its the ActionView thats complaining. Lets try and fix this then:
def after_save(tip)
...
template_instance = ActionView::Base.new(Rails::Configuration.new.view_path)
template_instance.extend ActionController::UrlWriter
Errors:
undefined method `default_url_options' for ActionView::Base:Class
Great so whatever we do we end up with errors. I've tried many many way of assigning default_url_options
inside the template_instance
without success.
So far this doesn't feel very "Railsy", in fact it feels downright difficult.
So my question is:
- Am I trying to get a square peg in a round hole? If so, how should I adapt the architecture to provide this functionality? I can't believe its not something that exists in other websites.
- Should I give up trying to use an Observer or Sweeper?
- Should I be trying to create new Messages via the MessagesController, and if so, how can I invoke the MessagesController directly and multiple times from within the Observer/Sweeper?
Any tips advice or suggestions would be very gratefully recieved, I have been banging my head against this brick wall for several days now and slowly losing the will to live.
tia
Keith