4

Let's say I have two models that both have same callbacks:

class Entry < ActiveRecord::Base
    belongs_to :patient
    validates :text, presence: true
    after_validation :normalizeDate

    def normalizeDate
      self.created_at = return_DateTime(self.created_at)
    end
end

class Post < ActiveRecord::Base
    after_validation :normalizeDate

    def normalizeDate
      self.created_at = return_DateTime(self.created_at)
    end
end

Where can I put the shared callback code? Thanks

 def normalizeDate
   self.created_at = return_DateTime(self.created_at)
 end
wpp
  • 7,093
  • 4
  • 33
  • 65
John Smith
  • 6,105
  • 16
  • 58
  • 109
  • 1
    It's not validation code. It's callback. – Marek Lipka May 09 '14 at 08:18
  • Sorry! I will change my question – John Smith May 09 '14 at 08:19
  • I think this is a good question but just a side-note: If you only have 2 models, duplication is fine. – wpp May 09 '14 at 08:42
  • 1
    @sytycs Dry is Dry my friend :) 2 times the same code is already duplication, just imagine your self having to maintain your code few months later, and forgot to update one of the 2? – Benjamin Bouchet May 09 '14 at 08:49
  • @BenjaminSinclaire I disagree. If in 2 months time I decide to change the way my Dates are normalised I will probably think about the 2 places in my code where that happens. I just don't like to jump the gun on refactoring. See: http://stackoverflow.com/questions/2298272/how-much-duplicated-code-do-you-tolerate – wpp May 09 '14 at 09:53
  • It's your right to disagree :) In my experience it never work like that. You will come back on your code most of the time, and most of the time your mind will be focus somewhere else. – Benjamin Bouchet May 09 '14 at 10:20
  • @BenjaminSinclaire haha could be :) thanks for being a good sport. – wpp May 09 '14 at 16:48

3 Answers3

8

Marek's answer is good but the Rails way is:

module NormalizeDateModule
  extend ActiveSupport::Concern

  included do
    after_validation :normalize_date
  end

  def normalize_date
    self.created_at = return_DateTime(created_at)
  end
end

Doc here.

(and you have a decicated folder for it: models/concerns)

apneadiving
  • 114,565
  • 26
  • 219
  • 213
3

You can define your own module:

module NormalizeDateModule
  def self.included(base)
    base.class_eval do
      after_validation :normalize_date
    end
  end

  def normalize_date
    self.created_at = return_DateTime(created_at)
  end
end

and include it in every class you want this behavior:

class Entry < ActiveRecord::Base
  include NormalizeDateModule
  # ...
end

I'm not sure if my code is free of error (I didn't test it), treat it as an example.

Marek Lipka
  • 50,622
  • 7
  • 87
  • 91
  • 1
    The above code will work if you make it a Module instead of `class NormalizeDateModule` and include. Refer to: http://stackoverflow.com/questions/1698225/where-to-put-common-code-found-in-multiple-models which also uses the above code. The user had marked it as valid answer. – SreekanthGS May 09 '14 at 08:23
  • @SreekanthGS of course. I meant `module` yet I wrote `class`. :) Thanks. – Marek Lipka May 09 '14 at 08:25
  • Sorry for this question, but where do i have to save the modul? I mean in the `models`-folder as `normalize_date_module.rb`? – John Smith May 09 '14 at 08:25
  • 1
    @JohnSmith yes, I think `models` folder is most appropriate for this. And yes, it should be named as you wrote. – Marek Lipka May 09 '14 at 08:26
  • 1
    @JohnSmith no, since it is meant to be reused over many models, it should be placed in separate file. – Marek Lipka May 09 '14 at 08:27
  • To test your solution i put `self.created_at = self.created_at + 2.days` into `def normalize_date`. But i always get the error: `undefined method `+' for nil:NilClass` because `self.created` seems not to be defined! What do i wrong? Thanks – John Smith May 09 '14 at 09:10
  • @JohnSmith `created_at` is `nil` before saving record. – Marek Lipka May 09 '14 at 09:12
  • I dont think so because it is set with the parameters, and with my old validtion i worked! – John Smith May 09 '14 at 09:15
  • @JohnSmith but your error message clearly states that `created_at` is `nil`. I'm quite sure this error doesn't come from this callback calling way. – Marek Lipka May 09 '14 at 09:17
2

The Rails 4 way is to use ActiveSupport::Concern

File models/concerns/date_normalizer.rb

module Concerns
  module DateNormalizer
    extend ActiveSupport::Concern

    included do |base|
      base.after_validation :normalize_date
    end

    def normalize_date
      self.created_at = return_DateTime(self.created_at)
    end
  end
end

File model/entry.rb

class Entry < ActiveRecord::Base
  include Concerns::DateNormalizer

  belongs_to :patient
  validates :text, presence: true
end

File models/post.rb

class Post < ActiveRecord::Base
  include Concerns::DateNormalizer
end

Note: I renamed for you normalizeDate to normalize_date

Benjamin Bouchet
  • 12,971
  • 2
  • 41
  • 73
  • So far nicest answer! Ill check it out! – John Smith May 09 '14 at 08:44
  • There is already a folder `concerns` in `models` should i put the `date_normalizer.rb` there? Thanks – John Smith May 09 '14 at 08:46
  • 1
    Sorry it's an habit of mine to have several concerns directories, better to use the rails default for a start – Benjamin Bouchet May 09 '14 at 08:47
  • Sorry but i get a error for your code: `wrong argument type Class (expected Module)` for: `include Concerns::DateNormalizer`. Do you know how to fix this? Thanks – John Smith May 09 '14 at 08:53
  • Yes off course, it's `module DateNormalizer` instead of `class DateNormalizer`. My bad. I edited my comment – Benjamin Bouchet May 09 '14 at 08:55
  • To test your solution i put `self.created_at = self.created_at + 2.days` into `def normalize_date`. But i always get the error: `undefined method `+' for nil:NilClass` because `self.created` seems not to be defined! What do i wrong? Thanks – John Smith May 09 '14 at 09:09
  • 1
    That's another problem: I assume you are testing on a new record, and for new records, at the time of validation (because your callback is `after_validation`) the field `created_at` is not yet set by rails – Benjamin Bouchet May 09 '14 at 09:13
  • 2
    The fact that you have this error means that your callback is correctly working (because the error is from your code in the call back) – Benjamin Bouchet May 09 '14 at 09:14
  • Thanks! What could i do so that it also works for new records? – John Smith May 09 '14 at 09:17
  • 1
    Kindly validate this answer and post another question, or it will become confusing – Benjamin Bouchet May 09 '14 at 09:19
  • Because its not related to your original question, let's keep stack overflow clean :) – Benjamin Bouchet May 09 '14 at 09:20