Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to put common code found in multiple models?

I have two models that contain the same method:

def foo
  # do something
end

Where should I put this?

I know common code goes in the lib directory in a Rails app.

But if I put it in a new class in lib called 'Foo', and I need to add its functionality to both of my ActiveRecord models, do I do that like this:

class A < ActiveRecord::Base
includes Foo

class B < ActiveRecord::Base
includes Foo

and then both A and B will contain the foo method just as if I had defined it in each?

like image 576
Bryan Locke Avatar asked Nov 08 '09 22:11

Bryan Locke


5 Answers

Create a module, which you can put in the lib directory:

module Foo
  def foo
    # do something
  end
end

You can then include the module in each of your model classes:

class A < ActiveRecord::Base
  include Foo
end

class B < ActiveRecord::Base
  include Foo
end

The A and B models will now have a foo method defined.

If you follow Rails naming conventions with the name of the module and the name of the file (e.g. Foo in foo.rb and FooBar in foo_bar.rb), then Rails will automatically load the file for you. Otherwise, you will need to use require_dependency 'file_name' to load your lib file.

like image 162
Phil Ross Avatar answered Nov 06 '22 13:11

Phil Ross


You really have two choices:

  1. Use a module for common logic and include it in A & B
  2. Use a common class C that extends ActiveRecord and have A & B extend C.

Use #1 if the shared functionality is not core to each class, but applicable to each class. For example:

(app/lib/serializable.rb)
module Serializable
  def serialize
    # do something to serialize this object
  end
end

Use #2 if the shared functionality is common to each class and A & B share a natural relationship:

(app/models/letter.rb)
class Letter < ActiveRecord::Base
  def cyrilic_equivilent
    # return somethign similar
  end
end

class A < Letter
end

class B < Letter
end
like image 35
Aaron Rustad Avatar answered Nov 06 '22 14:11

Aaron Rustad


Here's how I did it... First create the mixin:

module Slugged
  extend ActiveSupport::Concern

  included do
    has_many :slugs, :as => :target
    has_one :slug, :as => :target, :order => :created_at
  end
end

Then mix it into every model that needs it:

class Sector < ActiveRecord::Base
  include Slugged

  validates_uniqueness_of :name
  etc
end

It's almost pretty!

To complete the example, though it's irrelevant to the question, here's my slug model:

class Slug < ActiveRecord::Base
  belongs_to :target, :polymorphic => true
end
like image 43
bronson Avatar answered Nov 06 '22 12:11

bronson


One option is to put them in a new directory, for example app/models/modules/. Then, you can add this to config/environment.rb:

Dir["#{RAILS_ROOT}/app/models/modules/*.rb"].each do |filename|
  require filename
end

This will require every file in in that directory, so if you put a file like the following in your modules directory:

module SharedMethods
  def foo
    #...
  end
end

Then you can just use it in your models because it will be automatically loaded:

class User < ActiveRecord::Base
  include SharedMethods
end

This approach is more organized than putting these mixins in the lib directory because they stay near the classes that use them.

like image 5
nicholaides Avatar answered Nov 06 '22 12:11

nicholaides


If you need ActiveRecord::Base code as a part of your common functionalities, using an abstract class could be useful too. Something like:

class Foo < ActiveRecord::Base
  self.abstract_class = true
  #Here ActiveRecord specific code, for example establish_connection to a different DB.
end

class A < Foo; end
class B < Foo; end

As simple as that. Also, if the code is not ActiveRecord related, please find ActiveSupport::Concerns as a better approach.

like image 5
Ron Avatar answered Nov 06 '22 13:11

Ron