views:

151

answers:

5

I have several controllers that take an instance of different classes each (Email, Call, Letter, etc) and they all have to go through this same substitution:

@email.message.gsub!("{FirstName}", @contact.first_name)
@email.message.gsub!("{Company}", @contact.company_name) 
@email.message.gsub!("{Colleagues}", @colleagues.to_sentence)
@email.message.gsub!("{NextWeek}", (Date.today + 7.days).strftime("%A, %B %d"))
@email.message.gsub!("{ContactTitle}", @contact.title )

So, for example, @call.message for Call, @letter.message for Letter, etcetera.

This isn't very dry. I tried the following:

class ApplicationController < ActionController::Base
  helper :all # include all helpers, all the time

  def message_sub(asset, contact, colleagues)
    asset.message.gsub!("{FirstName}", contact.first_name)
    asset.message.gsub!("{Company}", contact.company_name) 
    asset.message.gsub!("{Colleagues}", colleagues.to_sentence)
    asset.message.gsub!("{NextWeek}", (Date.today + 7.days).strftime("%A, %B %d"))
    asset.message.gsub!("{ContactTitle}", contact.title )
  end
end

So in, say, the Letter Controller have this:

@letter = Letter.find(params[:letter]) #:letter was passed as a hash of the letter instance

message_sub(@letter, @contact, @colleagues)

@contact_letter.body = @letter.body

But the above doesn't work.

+1  A: 

I think we are missing information needed to fix this problem. Check out section 3 of this guide: Debugging Rails Applications

Step through your code and check the variables at each point to see if they are what you expect.

When you have time, be sure to read the whole guide. It provides an invaluable set of tools for Rails programming.

As a side note, the code you have provided feels like it is more suited to be in the model than in the controller. Your models may be a good candidate for Single Table Inheritance or your models are so much alike that you can condense them into a more generalized model that covers what you need. If either of these options are a good fit, you could move message_sub out of the controller and into the model.

Awgy
thanks, I guess I was looking for guidance on how to do it...in terms of passing the class or asset name in
Angela
@Angela: I changed my answer to link to a guide that will help you figure this out.
Awgy
Hi....I did a the debugging, but I guess I'm still trying to figure out whether to make this a view method (and if so, how)....I'm not sure it's ready for STI....
Angela
@Angela: You only need to make it a helper method if you are going to call it in the view. So, when you used the debugger, were you able to step into the call to message_sub and see the gsubs on @letter? After the method was called, was @letter modified as you expected?
Awgy
And you make it a helper method (available in the view) by using `helper_method :message_sub` in the ApplicationController (where your message_sub method is defined).
Awgy
okay, I see, although should I use this in the controller, not the view?
Angela
@Angela: It sounds like business logic, so it would be something I try to move into the model. I wouldn't focus on this too much until you have a handle on what's happening in the message_sub method as you have it right now.
Awgy
I see...would you recommend moving this into the model? If so, how would you suggest I do it? Thanks.
Angela
A: 

The problem may lie in the fact that gsub! works on the instance of the String object returned by ActiveRecord. To notify ActiveRecord of an attribute change, you should call the method message=(value). So, your method would be like this:

def message_sub(asset, contact, colleagues)
  asset.message = asset.message.
      gsub("{FirstName}", contact.first_name).
      gsub("{Company}", contact.company_name).
      gsub("{Colleagues}", colleagues.to_sentence).
      gsub("{NextWeek}", (Date.today + 7.days).strftime("%A, %B %d")).
      gsub("{ContactTitle}", contact.title )
end

(I took some artistic license to chain the gsub method, but it's all in your tastes)

Chubas
yes, I think I had that method= earlier and dropped it cutting and pasting, but I also appreciate you refining the gsub, I'm always looking for better ways to write the code. Let me try this out.
Angela
If you try the gsub! method Angela is using in the console, it does modify the message properly and return a modified version of the record. You can't save the changes to @letter, but if I understand what she's trying to do, the changes will be thrown out after the modified message is copied into the @contact_letter, so the letter's template can be reused.
Awgy
A: 

It's not clear if the question is about reusing a method across the controller (as the title suggests) or the best way to do message templates. To reuse the method across the controller , I guess you already answer the question yourself by using a common class. However as Awgy suggested this bit could better be in the Model than in the Controller.

To template message you can use ERB rather gsub or just 'eval' your string (if you use #{} instead of {} in your message)

firstName=contact.first_name
company = company_name
...
asset.message = eval "\"#{asset.message}\""

(You might even be able to pass directly contact as a 'bindings' to the eval function.

mb14
A: 

Personally, I wouldn't delegate this to the controller, isn't this something that the Presenter Pattern is for?

Omar Qureshi
+2  A: 

To answer your question directly, you want a module. You can put it in your lib directory.

module MessageSubstitution

  def message_sub(asset, contact, colleagues)
    asset.message.gsub!("{FirstName}", contact.first_name)
    asset.message.gsub!("{Company}", contact.company_name) 
    asset.message.gsub!("{Colleagues}", colleagues.to_sentence)
    asset.message.gsub!("{NextWeek}", (Date.today + 7.days).strftime("%A, %B %d"))
    asset.message.gsub!("{ContactTitle}", contact.title )
  end

end

Then in your controller

class MyController < ApplicationController

  include MessageSubstitution


  def action

    ...

    message_sub(@letter, @contact, @colleagues)

  end

end

However, as some have already pointed out, this is probably better at the model level:

module MessageSubstitution

  def substituted_message(contact, colleagues)
    message.gsub("{FirstName}", contact.first_name).
           gsub("{Company}", contact.company_name).
           gsub("{Colleagues}", colleagues.to_sentence).
           gsub("{NextWeek}", (Date.today + 7.days).strftime("%A, %B %d")).
           gsub("{ContactTitle}", contact.title )
  end

end

Which you would then include at the model level, and then call in your controller like @letter.substituted_message(@contact, @colleagues)

However, I'm a little confused that what you posted didn't work, though. If it's defined on ApplicationController it should work. Module-based solution still better IMO.

seriousken
Mixing in the message substitution logic is a great way to go. Jamis Buck wrote an article with more details here: http://weblog.jamisbuck.org/2007/1/17/concerns-in-activerecord
Benson
I see, so this is better as a Module at th emodel level....because that way whatever view or controller that calls that model I can just use this method as described, right?
Angela
so for me to use this across different models -- I need to apply this substituion for Letter, Email, and Calls and Postcards odels....so I just want to apply one module onto whatever model...
Angela
does the 'messsage' in message.gsub means all of my Models have to have an attribute message which contains the text that is being substituted?
Angela
Hmmm...get an error: undefined method `substituted_message' for #<Email:0x62d8c90>
Angela
I added include for the name fo the .rb file in /lib:uninitialized constant ContactEmailsController::MessageSubstitution
Angela
how is the asset passed in this version?
Angela
sorry, cna't seem to get this to work......is it right? It isn't recognizing the method for the model
Angela
The "message" in there does refer to the attribute. You would only include the module into models that have a message column, or you could get fancier and abstract it somehow, but it's easy enough to make an alias if the column has a different name.I'm not sure why you're not able to include the module. If the file is <RAILS_ROOT>/lib/message_substitution.rb, then include MessageSubstitution should work. Might require an explicit "require 'message_substitution'" at the top of the file.Oh, and asset in the message signature is a copy/paste error on my part. :)
seriousken
okay, let me try this again...I guess I don't know what is the "message substitution" and the copy/paste error.....so basically if I put this module I can use it on any model that has a message attribute?
Angela
oh wait, I see, it looks like I need to do an 'include MessageSubstitution' for each Model class?
Angela
hmm...yeah I did include MessageSubstitution and I get uninitialized constant Email::MessageSubstitution
Angela
ahaha! yes, you are right it works! thanks!
Angela