可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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.
回答2:
You really have two choices:
- Use a module for common logic and include it in A & B
- 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
回答3:
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
回答4:
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.
回答5:
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.
回答6:
As others have mentioned include Foo is the way to do things... However it doesn't seem to get you the functionality you want with a basic module. The following is the form a lot of Rails plugins take to add class methods and new callbacks in addition to new instance methods.
module Foo #:nodoc:
def self.included(base) # :nodoc:
base.extend ClassMethods
end
module ClassMethods
include Foo::InstanceMethods
before_create :before_method
end
module InstanceMethods
def foo
...
end
def before_method
...
end
end
end