Ruby on Rails: shared method between models - ruby-on-rails

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 :)

Related

Loading module into User Model in Rails

Trying to make available the methods I have stored in a Module which is located in app/models (side note: not sure if this is the correct place for modules?).
Module:
module MyModule
class MyClass
def some_method
# do something
end
end
end
User Model:
class User < ApplicationRecord
include MyModule
def another_method
some_method
end
end
I am getting a NoMethodError:
NoMethodError (undefined method 'some_method' for #<User:0x00007f6a3ce452c0>
You seem to have missunderstood what what modules and classes do in Ruby. In Ruby a module is simply an object that wraps a set of methods and constants.
A module can extend other modules, classes and objects and can be included in classes thus implementing multiple inheritance. Modules in Ruby fill the role that traits, namespaces and singletons do in other languages.
Classes are actually modules (Module is part of the ancestors chain of Class) with the key difference that you can make instances of a class and that class can inherit from a single other class and cannot extend other objects or be included.
The code example here actually doesn't make sense. If you want to declare a method that will be available to classes that include a module you want to declare it in the module itself:
module MyModule
def some_method
# do something
end
end
When you then call User#another_method it will look in the ancestors chain of the User class until it finds the method which is defined in MyModule.
module MyModule
class MyClass
def some_method
# do something
end
end
end
Will actually definte the class MyClass with an instance method that is only available to instances of MyClass. The only thing that the module does here is change the module nesting so that the class is defined in MyModule instead of the global namespace.
If you want to mix in a method from a method into your class then just put the methods directly in the module (without an intermediate class).
Module:
module MyModule
def some_method
# do something
end
end
User Model:
class User < ApplicationRecord
include MyModule
def another_method
some_method
end
end
Have a look at this answer, you need to instantiate your Class first. Or if you want to
class User < ApplicationRecord
include MyModule
def another_method
my_instance = MyClass.new
my_instance.some_method
end
end
As for a place to store your Module, have a look at this guide about service objects, it gave me some inspiration when it comes to different modules.

Having trouble with specs for a Ruby mixin module

I don't have a great experience with mixin modules. Then, please forgive me if my question seems to be a bit naïve.
I am creating a few modules to integrate a project with music services like Spotify, who have REST APIs. All these modules include another mixin module I created named APIClientBuilder, which provides a small DSL for creating API endpoints.
lib/integrations/api_client_builder.rb
require 'rest-client'
module APIClientBuilder
attr_accessor :api_client, :endpoint, :url, :param
def api_client(api_name)
end
def fetch_client(api_name)
end
def api_endpoint(endpoint_name)
end
def fetch_endpoint(api_name,endpoint_name)
end
def method=(meth)
end
def url=(endpoint_url)
end
def param(param_name,param_value)
end
def call(api_name,api_endpoint,token,*extra_params)
end
end
lib/integrations/spotify.rb
require_relative 'api_client_builder'
module SpotifyIntegration
include APIClientBuilder
def base_url
'https://api.spotify.com/v1'
end
def random_state_string
(0..10).map { (65 + rand(26)).chr }.join
end
api_client('spotify') do |apic|
apic.api_endpoint('request_authorization') do |ep|
ep.method = :get
ep.url = "https://accounts.spotify.com/authorize"
ep.param("client_id",SPOTIFY_KEY)
ep.param("response_type","code")
ep.param("redirect_uri","http://localhost:3000")
end
apic.api_endpoint('my_playlists') do |ep|
ep.method = :get
ep.url = "#{base_url}/me/playlists"
end
end
end
My idea was having in my controllers something like this:
app/controllers/api/v1/users_controller.rb
require 'integrations/spotify.rb'
class UsersController < ApplicationController
include SpotifyIntegration
end
And then have access to the methods in SpotifyIntegration and, through this, to the methods in APIClientBuilder.
It happens that I wrote the following spec file with a very simple test:
spec/lib/integrations/spotify_integration_spec.rb
require 'rails_helper'
require 'integrations/spotify'
class SpotifyClientTester
include SpotifyIntegration
end
RSpec.describe SpotifyIntegration do
context 'Auxiliary methods' do
it 'Two calls to random_state_string shall generate two different strings' do
obj = SpotifyClientTester.new
s1 = obj.random_state_string
s2 = obj.random_state_string
expect(s1).not_to eq(s2)
end
end
end
But when I run it I get
undefined local variable or method base_url for SpotifyIntegration:Module (NameError)
I am not sure about what I am missing. Maybe I should use extend instead of include. I always make some confusion about this.
Can someone put me in the right path? I've been fighting this error for a whole afternoon.
You're misusing mixins. Use mixins for cases where classical inheritance is not suited to add a set of features to objects.
For example:
module Commentable
extend ActiveSupport::Concern
included do
has_many :comments, as: :commentable
end
# ...
end
class Video < ApplicationRecord
include Commentable
end
class Hotel < ApplicationRecord
include Commentable
end
As you can see by this example you extend a module with other modules and include modules in classes. Using classical inheritance to add the shared behaviour would be awkward at best since the two classes are apples and pears.
In your specific case you should instead use classical inheritance and not mix the API client into the controller. Rather you controller should invoke it as a distinct object.
class APIClient
# Implement shared behavior for a REST api client
end
class SpotifyClient < APIClient
# ...
end
class FoosController < ApplicationController
def index
client = SpotifyClient.new
#foos = client.get_something
end
end
Why shouldn't you mix a API client into a controller or model? Because of the Single Responsibility Principle and the fact that using smaller parts that do a limited amount of things is preferable to creating god classes.
You need to extend APIClientBuilder if you want to use the methods defined here at class level in module SpotifyIntegration.
module SpotifyIntegration
extend APIClientBuilder
Also, base_url must be a class method too, def self.base_url

Rails - Best practice for abstract class definition and file naming

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

How to dynamically generate association names?

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.

Make all Rails models inherit from certain class

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)

Resources