Dynamically generate scopes in rails models
you could do
class Product < ActiveRecord::Base
[:small, :medium, :large].each do |s|
scope s, where(size: s)
end
end
but I personally prefer:
class Product < ActiveRecord::Base
scope :sized, lambda{|size| where(size: size)}
end
How can i write a method that can generate scope dynamically which can be used for multiple models in ruby on rails
I strongly discourage you from adding a scope for each single attribute. where(...)
is only 5 characters and provides additional context to the reader. Person.where(name: 'John Doe')
says: on Person
execute a query (where
) and return a collection that matches the criteria name: 'John Doe'
.
If you add the suggest attribute scope the line becomes Person.name('John Doe')
. By removing the context that this is a query a reader must "learn" that each attribute name can also be accessed as a scope.
The above immediately shows another issue, which is name conflicts. Person.name
is already taken, and returns the class name. So adding scope :name, ->(name) { where(name: name) }
will raise an ArgumentError.
Scopes can be useful, but when used too much they clutter the class method namespace of the model.
With the above out of the way, here are some actual solutions.
You could write a helper that allows you to easily create scopes for attributes. Then loop through the passed attributes and dynamically create scopes for them.
class ApplicationRecord < ActiveRecord::Base
class << self
private
def attribute_scope_for(*attributes)
attributes.each { |attr| scope attr, ->(value) { where(attr => value) } }
end
end
end
Then call this helper in your models.
class YourModel < ApplicationRecord
attribute_scopes_for :status, :portfolio_id # ....
end
Alternatively, if you want to create a scope for each attribute you can dynamically collect them using attribute_names
. Then loop through them and create scopes based on the names.
class ApplicationRecord < ActiveRecord::Base
class << self
private
def enable_attribute_scopes
attribute_names
.reject { |attr| respond_to?(attr, true) }
.each { |attr| scope attr, ->(value) { where(attr => value) } }
end
end
end
In the above snippet .reject { |attr| respond_to?(attr, true) }
is optional, but prevents the creation of scopes that have a name conflict with a current public/private class method. This will skip those attributes. You can safely omit this line, but the scope
method might raise an ArgumentError when passing dangerous scope names.
Now the only thing left to do is calling enable_attribute_scopes
in models where you want to enable attribute scopes.
The above should give you an idea of how you could handle things, you could even add options like :except
or :only
. There is also the option to extract the code above into a module and extend AttributeScopeHelpers
within ApplicationRecord
if the class becomes to cluttered.
However, like I started this answer I would advise against adding scopes for each attribute.
Dynamically Generating Scopes with Descendants of a Model
The problem is that descendant classes are not instantiated. The following workaround will help you to achieve, what you're looking for:
def self.types
ActiveRecord::Base.connection.tables.map {|t| t.classify.constantize rescue nil}.compact
descendants.map(&:name)
end
But personally, I don't think you need these scopes at all, since calling all
on the descendant classes will provide the query:
Faculty.all
is the same as Unit.where(type: "Faculty")
Academy.all
is the same as Unit.where(type: "Academy")
Dynamic scope with or method in Rails 5
class Unit < ApplicationRecord
UnitType.groups.each do |unit_type|
scope ActiveSupport::Inflector.pluralize(unit_type), -> {
where(unit_type: unit_type)
}
end
scope :by_multiple_unit_types, ->(unit_types) {
int_unit_types = unit_types.map { |ut| UnitType.groups.index(ut) }.join(',')
where("unit_type IN (?)", int_unit_types)
}
end
How to Dynamically Build an Active Record Query with Rails Scopes
In a model method:
def self.multi_search(your_params)
scope = Model.scoped({})
your_params.split(' ').map{|v| scope = scope.search(v)}
scope
end
How do I create a dynamic scope where I can pass in an object if possible?
You have 2 options here.
Creating a class method on the
Post
model, as you suggested. Don't forget to define it asdef self.posted_days_ago(n)
and useself
inside the method to represent the class.Use a parameter inside a scope. It will look something like this:
scope :posted_num_days_ago, ->(n=1) {where(created_at: ((Time.now.midnight - n.days)..(Time.now.midnight - (n-1).days)))}
Notice the default value I set in this example. You may set it to whatever you need.
Related Topics
Sleep Until Condition Is True in Ruby
How to Pass <Arguments> to Irb If I Don't Specify <Programfile>
Run Code Only If Script Called from the Command Line
Ruby on Rails Model Inside Namespace Can't Be Found in Controller
How to Do Named Capture in Ruby
Gem Install Mongrel Fails with Ruby 1.9.1
How to Pass a Variable to a Layout
Difference Between String.Scan and String.Split
Ruby, Run Linux Commands One by One, by Ssh and Log Everything
Xpath to Find All Following Siblings Up Until the Next Sibling of a Particular Type
Rails Nested With_Option :If Used in Validation
How to Stop God from Leaving Stale Resque Worker Processes
How to Mock Aws Sdk (V2) with Rspec
Including a Virtual Attribute in the Respond_With Hash
Is Assignment in a Conditional Clause Good Ruby Style
Ruby - Create Singleton with Parameters
What's the Cleanest Way to Override Activerecord's Find for Both Models and Collections
Can't Launch Simple Sinatra App Using Rackup and Jruby (No Response from Web Server)