Hacking Activerecord: Add Global Named Scope

Hacking ActiveRecord: add global named scope

Rails 5, ApplicationRecord (Hope it helps others)

# app/models/concerns/not_older_than.rb

module NotOlderThan
extend ActiveSupport::Concern

included do
scope :not_older_than, -> (time, table = self.table_name){
where("#{table}.created_at >= ?", time.ago)
}
end
end

# app/models/application_record.rb

class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
include NotOlderThan
end

# app/models/user.rb
class User < ApplicationRecord
# Code
end

# Usage
User.not_older_than(1.week)

In Rails 5, all models are inherited from ApplicationRecord by default. If you wan to apply this scope for only particular set of models, add include statements only to those model classes. This works for join queries and chained scopes as well.

Access named scope dynamically

You would call Foo.public_send(variable).

ActiveRecord list custom scopes

The ActiveRecord::Scoping::Named::ClassMethods#scope DSL helper just creates a new method, it does not store the scope name anywhere, so no, it’s not possible out of the box.

OTOH, one might easily provide such a functionality:

ActiveRecord::Scoping::Named::ClassMethods.prepend(Module.new do
def scope(name, body, &block)
(@__scopes__ ||= []) << name
super
end
end)

and then the instance variable on your class would be defined:

MyRecord.instance_variable_get(:@__scopes__)
#⇒ [:custom_scope_one, :custom_scope_two, :custom_scope_three]

You might also declare an accessor for this instance variable or whatever.

NB the code above is not tested, I only proved it looks ok.

Rails 4 named scope with record always at end

This is a little bit tricky. In SQL you can add CASE statements to your ORDER BY. In your case the SQL would be something similar to.

SELECT *
FROM categories
ORDER BY
(
CASE
WHEN name = 'Other' THEN 1
ELSE 0
END
)

Here's a live example.

As far as I know, the ActiveRecord order method accepts arbitrary string, so you could (not tested) be able to pass the case to the method

Category.order("CASE WHEN name = 'Other' ... ")

This approach seems complicated, but if you can get it to work is by far the most efficient.

The second alternative is to play a little bit with ActiveRecord.

class Category < ActiveRecord::Base

def self.ordered(condition = nil)
# Get the ID of the record to exclude
excluded = self.where(name: 'Other').pluck(:id).first
# order the records and collect the ids
ids = where('id <> ?', excluded).order(condition).pluck(:id)
# append the excluded at the end
ids << excluded

# recreate the scope and return it.
where(id: ids)
end

end

Category.where(...).ordered

Generally speaking, I encourage you to avoid default_scopes in ActiveRecord. It's so easy to add them, but very hard to remove them when you need.

Rails3: combine scope with OR

From Arel documentation

The OR operator is not yet supported. It will work like this:
users.where(users[:name].eq('bob').or(users[:age].lt(25)))

This RailsCast shows you how to use the .or operator. However, it works with Arel objects while you have instances of ActiveRecord::Relation.
You can convert a relation to Arel using Product.name_a.arel, but now you have to figure out how to merge the conditions.

How can I use multiple scopes to construct a query?

Found it!

Looks like merge can be used:
http://api.rubyonrails.org/classes/ActiveRecord/SpawnMethods.html#method-i-merge

Equivalent to += (plus equal) to add scopes with ActiveRecord in rails

Chain them like this

@list = Product.all
@list = @list.scope1 if condition1
@list = @list.scope2 if condition2
@list

Then in the end @list holds the elements you want.

Programmatically apply an ActiveRecord scope that accepts arguments

Object#send accepts the name of the method and its arguments:

name = :age_range
low = 45
high = 55
Employee.send(name, low, high)

I would recommend to use Object#public_send, to make sure you only use publicly accessible API:

Employee.public_send(name, low, high)


Related Topics



Leave a reply



Submit