I am using Ruby on Rails 3.2.2 and the Squeel gem. I have following statements and I am trying to refactoring the my_squeel_query method in a Mixin module (since it is used by many of my models):
# Note: 'article_comment_associations' and 'model_as_like_article_comment_associations'
# refer to database table names.
class Article < ActiveRecord::Base
def my_squeel_query
commenters.
.where{
article_comment_associations.article_id.eq(my{self.id}) & ...
}
end
end
class ModelAsLikeArticle < ActiveRecord::Base
def my_squeel_query
commenters.
.where{
model_as_like_article_comment_associations.article_id.eq(my{self.id}) & ...
}
end
end
My problem is that I can not refactoring article_comment_associations and model_as_like_article_comment_associations statements by generating a dynamic name in the Mixin module. That is, if that was a String I could dynamically generate the related name by using something like "#{self.class.to_s.singularize}_comment_associations" as the following:
class Article < ActiveRecord::Base
include MyModule
end
class ModelAsLikeArticle < ActiveRecord::Base
include MyModule
end
module MyModule
def my_squeel_query
commenters.
.where{
# Note: This code doesn't work. It is just an sample.
"#{self.class.to_s.singularize}_comment_associations".article_id.eq(my{self.id}) & ...
}
end
end
But, since it is not my case, I cannot "build" the name and make the my_squeel_query to be "shared" across models.
How can I dynamically generate association names related to the Squeel gem? Should I think to refactoring in another way? What do you advice about?
Since the DSL is instance_evaled, you can actually say something like:
def my_squeel_query
base = self
commenters.
.where{
# Note: This code does work. Because it's awesome.
__send__("#{base.class.to_s.singularize}_comment_associations").
article_id.eq(my{self.id})
}
end
You can do this if you generate the methods dynamically. The Module.included method is provided for this purpose:
module ModuleAsLikeArticle
def self.included(base)
base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
# ...
end
end
end
This gets triggered when the module is imported with include and allows you to create methods specifically tailored for that.
As a note you might want to use base.name.underscore.singularize for a more readable method name. By convention, method names should not have upper-case in them, especially not as the first character.
Conventional Rails type applications use a different approach, though, instead defining a class method that can be used to create these on-demand:
module ModuleAsLikeArticle
def has_comments
base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
# ...
end
end
end
This would be used like this:
class ModelAsLikeArticle < ActiveRecord::Base
extend MyModule
has_comments
end
Since the method is not created until has_comments is called, you can safely extend ActiveRecord::Base and then insert the appropriate call in all the classes which require that functionality.
I think you might find what you need in the Rails Reflection class (http://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html), which, as the page says, allows you to interrogate ActiveRecord classes about their associations and aggregations.
Related
I want to define 3 classes:
a MotherClass (abstract, can not be inferred)
a SubClassA (inherits from MotherClass)
a SubClassB (inherits from MotherClass)
What is the best solution to declare it in Rails ?
1. Put everything in app/models/
MotherClass < AR::Base in app/models/mother_class.rb
SubClassA < MotherClass in app_models/sub_class_a.rb
SubClassB < MotherClass in app/models/sub_class_b.rb
Advantage: not very complicated to implement
Inconvenient: a big mess in models folder
2. Create a module for the two subclasses
MotherClass < AR::Base in app/models/mother_class.rb
MotherModule::SubClassA < MotherClass in app/models/mother_module/sub_class_a.rb
MotherModule::SubClassB < MotherClass in app/models/mother_module/sub_class_b.rb
Advantage: same than Solution 1
Inconvenient: naming MotherModule and MotherClass with different names, but they mean almost the same thing
3. Create a module for the 3 classes
MotherModule::Base < AR::Base in app/models/mother_module/base.rb
MotherModule::SubClassA < MotherModule::Base in app/models/mother_module/sub_class_a.rb
MotherModule::SubClassB < MotherModule::Base in app/models/mother_module/sub_class_b.rb
Advantage: very clean
Inconvenient: need some functions in Base to override (table_name for example)
So my question is: What is the best practice in Rails and
- how to name my classes?
- what are their directories?
First of all, I think you must already realize that ruby does not have true abstract classes. But we can approximate the behavior. And while doing so, it sounds like you have a preference toward organizational structure which I will attempt to address.
I must start by saying, however, that I'm surprised that you're coming at the problem so strongly from the organizational angle. First on my mind would be whether I really wanted to implement single table inheritance or not and then let that drive the organizational problem. Usually the answer here is that Single Table Inheritance is not what you actually want. But... let's dive in!
Using Single Table Inheritance
Here's the standard way to utilize and organize models using Single Table Inheritance:
# app/models/mother_class.rb
class MotherClass < ActiveRecord::Base
# An "abstract" method
def method1
raise NotImplementedError, "Subclasses must define `method1`."
end
def method2
puts method1 # raises NotImplementedError if `method1` is not redefined by a subclass
end
end
# app/models/sub_class_a.rb
class SubClassA < MotherClass
def method1
# do something
end
end
# app/models/sub_class_b.rb
class SubClassB < MotherClass
def method1
# do something
end
end
Given the above, we would get an exception when calling MotherClass.new.method2 but not when calling SubClassA.new.method2 or SubClassB.new.method2. So we've satisfied the "abstract" requirements. Organizationally, you called this a big mess in the models folder... which I can understand if you've got tons of these subclasses or something. But, remember that in single table inheritance even then parent class is a model and is / should be usable as such! So, that said, if you'd really like to organize your models file system better then you are free to do so. For example, you could do:
app/models/<some_organizational_name>/mother_class.rb
app/models/<some_organizational_name>/sub_class_a.rb
app/models/<some_organizational_name>/sub_class_b.rb
In this, we are keeping all other things (i.e. the Code for each of these models) the same. We're not namespacing these models in any way, we're just organizing them. To make this work it's just a matter of helping Rails to find the models now that we've placed them in a subfolder of the models folder without any other clues (i.e. without namespacing them). Please refer to this other Stack Overflow post for this. But, in short, you simply need to add the following to your config/application.rb file:
config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**/}')]
Using Mixins
If you decide that Single Table Inheritance is not what you want (and they often aren't, really) then mixins can give you the same quasi-abstract functionality. And you can, again, be flexible on file organization. The common, organizational pattern for mixins is this:
# app/models/concerns/mother_module.rb
module MotherModule
extend ActiveSupport::Concern
# An "abstract" method
def method1
raise NotImplementedError, "Subclasses must define `method1`."
end
def method2
puts method1 # raises NotImplementedError if `method1` is not redefined
end
end
# app/models/sub_class_a.rb
class SubClassA
include MotherModule
def method1
# do something
end
end
# app/models/sub_class_b.rb
class SubClassB
include MotherModule
def method1
# do something
end
end
With this approach, we continue to not get an exception when calling SubClassA.new.method2 or SubClassB.new.method2 because we've overridden these methods in the "subclasses". And since we can't really call MotherModule#method1 directly it is certainly an abstract method.
In the above organization, we've tucked MotherModule away into the models/concerns folder. This is the common location for mixins in Rails these days. You didn't mention what rails version you're on, so if you don't already have a models/concerns folder you'll want to make one and then make rails autoload models from there. This would, again, be done in config/application.rb with the following line:
config.autoload_paths += Dir[Rails.root.join('app', 'concerns', '{**/}')]
The organization with the mixins approach is, in my opinion, simple and clear in that SubclassA and SubClassB are (obviously) models and, since they include the MotherModule concern they get the behaviors of MotherModule. If you wanted to group the subclass models, organizationally, into a folder then you could still do this of course. Just use the same approach outlined at the end of the Single Table Inheritance section, above. But I'd probably keep MotherModule located in the models/concerns folder still.
Even though ruby doesn't really have abstract classes, it's powerful enough to let you implement it yourself by implementing self.included on a mixin module. Hopefully this generic example gives you enough to go on for your particular implementation.
module MotherInterface
def self.included base
required_class_methods = [:method1, :method2]
required_instance_methods = [:fizzle, :fazzle]
required_associations = [:traits, :whatevers]
required_class_methods.each do |cm|
raise "MotherInterface: please define .#{cm} class method on host class #{base.name}" unless base.respond_to?(cm)
end
required_instance_methods.each do |im|
raise "MotherInterface: please define ##{im} instance method on host class #{base.name}" unless base.instance_methods.include?(im)
end
required_associations.each do |ass|
raise "MotherInterface: please be sure #{base.name} has a :#{ass} association" unless base.reflections.key?(ass)
end
base.send :include, InstanceMethods
base.extend ClassMethods
end
# inherited instance methods
module InstanceMethods
def foo
end
def bar
end
end
# inherited class methods
module ClassMethods
def baz
end
def bat
end
end
end
class SubClassA < ActiveRecord::Base
include MotherInterface
# ... define required methods here ...
end
class SubClassB < ActiveRecord::Base
include MotherInterface
end
Some advantages to this approach are:
Yes, you can still technically instantiate the mixin, but it's not actually tied to active record, so it tastes more like an abstract class.
The sub classes get to define their own connection information. You have two databases? Differing columns? Cool, no problem. Just implement your instance methods and stuff appropriately.
The dividing line between parent and child is very obvious.
But, there are disadvantages too:
All the meta programming is a bit more complex. You'll have to think a little abstractly (HA!) about how to organize your code.
There are probably other advantages and disadvantages I haven't considered, kind of in a hurry here.
Now, as far as file locations, I would suggest that the mixin itself, presumably mother_interface.rb, go someplace other than your models folder.
In config/application.rb, throw in a line like this:
config.autoload_paths << File.join(Rails.root, 'app', 'lib')
...and then you can create (rails)/app/lib/mother_interface.rb. Really, you should do it however makes sense to you. I dislike the word "concerns" for this, and other people dislike the word "lib." So, use whatever word you like, or make up your own.
Using Single Table Inheritance with little bit meta programming
# app/models/mother_class.rb
class MotherClass < ActiveRecord::Base
def self.inherited(subclass)
subclass.include(OnlyChildMethods)
end
module OnlyChildMethods
extend ActiveSupport::Concern
included do
def child_method_one
puts 'hi one'
end
def child_method_two
puts 'hi two'
end
end
end
end
# app/models/sub_class_a.rb
class SubClassA < MotherClass
def some_specific_method
#some code
end
end
# app/models/sub_class_b.rb
class SubClassB < MotherClass
def some_specific_method
#some code
end
end
mother_class_instance.child_method_one
=> NoMethodError: undefined method 'child_method_one'
sub_class_a_instance.child_method_one
hi one
=> nil
I wrote an upsert method for one of my models. I would like all my models to have this upsert method. It seemed to me that the logical solution was to define a model that inherits from ActiveRecord::Base and then have all my other models inherit from that. But if I do that, Rails complains that the new model I created doesn't have a table to go with it, which is true, but I don't care.
Since the way I tried is apparently not the right way to do it, what's the right way to do it?
You can extend ActiveRecord with a module. you only do it in one place and it will be accessible for all models that inherits from ActiveRecord.
module YourModule
def self.included(recipient)
recipient.extend(ModelClassMethods)
recipient.class_eval do
include ModelInstanceMethods
end
end # #included directives
# Class Methods
module ModelClassMethods
# A method accessible on model classes
def whatever
end
end
# Instance Methods
module ModelInstanceMethods
#A method accessible on model instances
def another_one
end
end
end
#This is where your module is being included into ActiveRecord
if Object.const_defined?("ActiveRecord")
ActiveRecord::Base.send(:include, YourModule)
end
There are two ways to do this.
1) To have a parent model, but not need to create a table for it (i.e. an abstract class) you should set
class YourAbstractClass < ActiveRecord::Base
self.abstract_class = true
# rest of class code
end
2) Put the method in a module, that you include from all your models that need it (as in #Mark's answer)
You can move that method to a module and include that module in all the models that require that method.
Like I have this Utils module in lib folder of my app
module Utils
...
def to_name(ref)
ref.gsub('_', ' ').split.collect { |w| w.capitalize }.join(' ')
end
...
end
Then in my model, I say
class MyModel < AR::Base
include Utils
...
end
Probably, if you are using Rails 3, you should load the files in the lib folder by configuring your application.rb
config.autoload_paths += %W(#{config.root}/lib)
If a few of my models have a privacy column, is there a way I can write one method shared by all the models, lets call it is_public?
so, I'd like to be able to do object_var.is_public?
One possible way is to put shared methods in a module like this (RAILS_ROOT/lib/shared_methods.rb)
module SharedMethods
def is_public?
# your code
end
end
Then you need to include this module in every model that should have these methods (i.e. app/models/your_model.rb)
class YourModel < ActiveRecord::Base
include SharedMethods
end
UPDATE:
In Rails 4 there is a new way to do this. You should place shared Code like this in app/models/concerns instead of lib
Also you can add class methods and execute code on inclusion like this
module SharedMethods
extend ActiveSupport::Concern
included do
scope :public, -> { where(…) }
end
def is_public?
# your code
end
module ClassMethods
def find_all_public
where #some condition
end
end
end
You can also do this by inheriting the models from a common ancestor which includes the shared methods.
class BaseModel < ActiveRecord::Base
def is_public?
# blah blah
end
end
class ChildModel < BaseModel
end
In practice, jigfox's approach often works out better, so don't feel obligated to use inheritance merely out of love for OOP theory :)
I have a model, Show and a module Utilities
class Show < ActiveRecord::Base
include Utilities
...
def self.something
fix_url("www.google.com")
end
end
My Utilities file is in lib/utilities.rb
module Utilities
def fix_url(u)
!!( u !~ /\A(?:http:\/\/|https:\/\/)/i ) ? "http://#{u}" : u
end
end
But Rails is throwing a NoMethodError for "fix_url" when I call it in my show class. Do I have to do something different when including a module in my model?
Thanks!
try injecting that mixin via the extend instead of include. Basically, because you are calling the mixin method from a class method, but including a mixin only makes its instance methods available. You can use the extend style to get class methods.
Search around for Ruby include and extend to learn the differences. A common pattern is to do it like here:
http://www.dcmanges.com/blog/27
Where you use the included hook to mixin both instance and class level methods.
#Tony - this works for me
class User < ActiveRecord::Base
extend Utilities
def self.test
go()
end
end
module Utilities
def go
puts "hello"
end
end
From console:
>> User.test
hello
=> nil
At no point do I have to explicitly call a method with self.
It worked for me. Have you tried restarting your server/console session?
Edit: If you want to just call Utilities.fix_url you can do that - no include/extend necessary.
I have an issue with Ruby on Rails.
I have several model classes that inherit from the same class in order to have some generic behaviour.
The parent class is called CachedElement.
One of the child is called Outcome.
I want an other model, called Flow to belong to any child of CachedElement.
Hence Flow has a polymorphic attributes called element, to which it belongs_to
When I create a new flow, that belongs to an Outcome, the element_type is set to "CachedElement" which is the parent class, instead of "Outcome".
This is confusing because since I have several type of CachedElement which are stored in different tables, the element_id refers to several different element.
In short I would like the element_type field to refer to the child class name and not the parent class name.
How can I do that ?
The field element_type is set to the parent class because ActiveRecord expects you to use single-table inheritance when deriving from other models. The field will reference the base class because it refers to the table that each instance is stored in.
If the children of CachedElement are stored in their own tables, it may be more helpful to replace the use of inheritance with the use of Ruby modules. The standard approach for sharing logic between classes is to use mix-ins instead of inheritance. For example:
module Cacheable
# methods that should be available for all cached models
# ...
end
class Outcome < ActiveRecord::Base
include Cacheable
# ...
end
You can now easily use polymorphic associations as you have been doing already, and element_type will be set to the proper class.
the file should go on you lib folder. but...
you could do the inheritance thing as well.
all you need to do is to tell you parent class to act as an abstract class.
# put this in your parent class then try to save a polymorphic item again.
# and dont forget to reload, (I prefer restart) if your gonna try this in
# your console.
def self.abstract_class?
true
end
and thats pretty much it, this was kinda unespected for me and actually really
hard to find in the documentation and anywhere else.
Kazuyoshi Tlacaelel.
Thanks, that's what I did, but it was kind of tricky to be able to inherits both instance and class methods from the module
Class methods can be done by:
module Cachable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def a_class_method
"I'm a class method!"
end
end
def an_instance_method
"I'm an instance method!"
end
end
class Outcome < ActiveRecord::Base
include Cacheable
end
if you want to add class methods and instance methods through a mixin (Module)
then I recommend you to abstract these in different modules.
module FakeInheritance
def self.included(klass)
klass.extend ClassMethods
klass.send(:include, InstanceMethods)
end
module ClassMethods
def some_static_method
# you dont need to add self's anywhere since they will be merged into the right scope
# and that is very cool because your code is more clean!
end
end
module InstanceMethods
# methods in here will be accessable only when you create an instance
end
end
# fake inheritance with static and instance methods
class CachedElement
include FakeInheritance
end