views:

1427

answers:

3

Hi All,

I have methods in all of my models that look like this:

def formatted_start_date
  start_date ? start_date.to_s(:date) : nil
end

I would like to have something that automatically writes a method like this for each datetime field in each model, what's the best way to do this?

-C

+6  A: 

It looks more like something for a helper to me. Try this in the your application help:

def formatted_date(date)
   date ? date.to_s(:date) : nil
end

Formatting isn't something that really belongs in the model (for precisely the reason you've discovered... putting such common code in every model is annoying)

If you really want to do as you say though, then what you could do is monkeypatch the ActiveRecord superclass and add the function you want into there. It would then be available for all your models. Beware that monkeypatching can lead to unpredictable and undefined behaviour and use at your own risk! It's also pretty hacky :)

class ActiveRecord::Base
   def formatted_start_date
      start_date ? start_date.to_s(:date) : nil
   end
end

Just stick that somewhere that will be run before anything else in your app will be, and it will dynamically add the method to the base class of your models, making it available for use.

Or you could create a mixin for all your models, but that seems a bit overkill for a single method.

workmad3
+1 from me. Was just writing the same answer...
tomafro
okay, let's assume that it was appropriate to put the formatting in the model, or that I want to do something else besides formatting with each and every date field in my application, how would I do it?
Chris Drappier
yeah, but the 'start_date' part is hard-coded here, i need to do this for all date fields in the app, they could be called start_date, end_date, term_date, finish_date, date, etc.. how do i check the subclass for all date columns and then write a dynamic method for each one?
Chris Drappier
I don't know that... it just sounds like a bad idea :P
workmad3
A: 

An answer to your comment: You can extract common code/functionality into modules which you include in a class (I belive that is called mixin?) or you can go for subclasses. I don't think subclass is the way to go when its just common functionality you want to get into your objects, and not a real inheritance situation.

Take a look at this for more info on modules and Ruby.

Thorbjørn Hermansen
+4  A: 

I just had to answer this, cos it's a fun Ruby excercise.

Adding methods to a class can be done many ways, but one of the neatest ways is to use some of the reflection and evaluation features of Ruby.

Create this file in your lib folder as lib/date_methods.rb

module DateMethods

  def self.included(klass)

    # get all dates
    # Loop through the class's column names
    # then determine if each column is of the :date type.
    fields = klass.column_names.collect do |k| 
                  k if klass.columns_hash[k].type == :date
                end
    # collect throws in empty entries into the 
    # array if we don't meet the condition in the loop
    # so we should remove those nils.
    fields.compact!  # removes nils

    # for each of the fields we'll use class_eval to
    # define the methods.
    fields.each do |field|
      klass.class_eval <<-EOF
        def formatted_#{field}
          #{field} ? #{field}.to_s(:date) : nil
        end

      EOF
    end
  end
end

Now just include it into any models that need it

 class CourseSection < ActiveRecord::Base
   include DateMethods
 end

When included, the modeule will look at any date columns and generate the formatted_ methods for you.

Learn how this Ruby stuff works. It's a lot of fun.

That said, you have to ask yourself if this is necessary. I don't think it is personally, but again, it was fun to write.

-b-

Brian Hogan
why use collect and compact on the array rather than select?
Chris Drappier
You're right. .select would be more appropriate here. I've found times when .collect.compact is faster, even when it appears it shouldn't be, but in this case .select is a much better choice. Thanks for catching that. I was focusing on doing the hard stuff and missed the easy stuff.
Brian Hogan
+1 Interesting and fun stuff. Would be better if you corrected the answer (here on SO) to include Chris' comments.
Yar
I also vote for updated answer with use of `select` :)
macek