0

There is the following code for model:

class Place < ActiveRecord::Base
  attr_accessible :address, :name, :latitude, :longitude
  validates :name, :address, :latitude, :longitude, presence: true

  etc...
end

And there is some class which has the same validations methods:

class PlaceClub
  include Virtus

  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  attr_reader :club
  attr_reader :place

  attribute :name, String
  attribute :address, String
  attribute :latitude, Float
  attribute :longitude, Float

  validates :name, :address, :latitude, :longitude, presence: true

  etc ...
end

Can I move validating methods to the separate class in order to use it in both classes? Thanks in advance.

malcoauri
  • 11,904
  • 28
  • 82
  • 137
  • It is possible to do what you want, but it is very poor design. If you're using Rails 4, look into using concerns. – Max Nov 21 '13 at 06:43
  • I use Rails 3.2. Concerns are not available in this version, aren't they? – malcoauri Nov 21 '13 at 06:47
  • 1
    No they aren't but you can do essentially the same thing using a module. I'll post the answer in a second, but I strongly advise against doing this. Seriously, don't do it. – Max Nov 21 '13 at 06:49
  • But may be you post your idea about module? – malcoauri Nov 21 '13 at 06:53

2 Answers2

1

You can use ActiveSupport::Concern

Update your code like:

class Place < ActiveRecord::Base
  include CustomPlaceClubValidation
  attr_accessible :address, :name, :latitude, :longitude
  validates :name, :address, :latitude, :longitude, presence: true

  etc...
end

And (put this in /app/models/concerns/custom_place_club_validation):

module CustomPlaceClubValidation
  extend ActiveSupport::Concern

  include Virtus

  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  included do
    attr_reader :club
    attr_reader :place

    attribute :name, String
    attribute :address, String
    attribute :latitude, Float
    attribute :longitude, Float

    validates :name, :address, :latitude, :longitude, presence: true

    etc ...
  end
end
Alexander Randa
  • 868
  • 4
  • 7
0

Here is how to do what you want. To warn other readers: doing this is just asking for trouble. It is very fragile and and I can't think of why you would knowingly add something so fragile to your code, especially when a strong alternative is readily available. If you don't care about this warning, read on.

To implement this in Rails 3, you simply create a new module to store your common validator, and then use the include call back to use the validator on any class that includes the module.

First, create a module directory if you haven't already. I prefer lib/modules. Next, add the modules directory to your load path by editing config/application.rb

# config/application.rb
require File.expand_path('../boot', __FILE__)

# Pick the frameworks you want:
# ...

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env)

module AppName
  class Application < Rails::Application
    config.autoload_paths += %W(#{config.root}/lib) # <- Add this line

    # ...
  end
end

Next, create the module. For this example, I will name it the file common_validator.rb. Remember to place it in lib/modules.

# lib/modules/common_validator.rb
module CommonValidator
  def CommonValidator.included(mod)
    mod.validates :name, :what, :ever, :params
  end
end

Finally, you just include this module in your models. Like so:

class Place < ActiveRecord::Base
  attr_accessible :address, :name, :latitude, :longitude
  include CommonValidator # <- Add this

  etc...
end

There you have it. Just make sure to restart your Rails server in order to pick up the load path changes. You also have to restart the server whenever you make a change to the module. (Edit: Read this question to see how to reload the module on every request). Rails 4 has a nicer syntax for doing this type of thing in ActiveSupport::Concern. If you understand the example above, porting it into a concern is trivial.

Community
  • 1
  • 1
Max
  • 15,157
  • 17
  • 82
  • 127
  • So, if you strongly don't recommend to use this code and I can't use concerns - what should I do? – malcoauri Nov 21 '13 at 07:14
  • You should just use the same validator in both places. Even if you had concerns I wouldn't recommend them either. The reason why the above code is bad is that there is no guarantee `Place` and `PlaceClub` will always be the same. In fact, they are very likely to diverge at some point. So by merging the validators, you are just causing yourself a lot of grief down the line. – Max Nov 21 '13 at 07:24