I am trying to have a pack of very generic named scopes for ActiveRecord models like this one:
module Scopes
def self.included(base)
base.class_eval do
named_scope :not_older_than, lambda {|interval|
{:conditions => ["#{table_name}.created_at >= ?", interval.ago]
}
end
end
end
ActiveRecord::Base.send(:include, Scopes)
class User < ActiveRecord::Base
end
If the named scope should be general, we need to specify *table_name* to prevent naming problems if their is joins that came from other chained named scope.
The problem is that we can't get table_name because it is called on ActiveRecord::Base rather then on User.
User.not_older_than(1.week)
NoMethodError: undefined method `abstract_class?' for Object:Class
from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2207:in `class_of_active_record_descendant'
from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1462:in `base_class'
from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1138:in `reset_table_name'
from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1134:in `table_name'
from /home/bogdan/makabu/railsware/startwire/repository/lib/core_ext/active_record/base.rb:15:in `included'
from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/named_scope.rb:92:in `call'
from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/named_scope.rb:92:in `named_scope'
from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/named_scope.rb:97:in `call'
from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/named_scope.rb:97:in `not_older_than'
How can I get actual table_name at Scopes module?
Scopes are used to assign complex ActiveRecord queries into customized methods using Ruby on Rails. Inside your models, you can define a scope as a new method that returns a lambda function for calling queries you're probably used to using inside your controllers.
The active record pattern is an approach to accessing data in a database. A database table or view is wrapped into a class. Thus, an object instance is tied to a single row in the table. After creation of an object, a new row is added to the table upon save.
ActiveRecord is commonly used with the Ruby-on-Rails framework but you can use it with Sinatra or without any web framework if desired.
What is ActiveRecord? ActiveRecord is an ORM. It's a layer of Ruby code that runs between your database and your logic code. When you need to make changes to the database, you'll write Ruby code, and then run "migrations" which makes the actual changes to the database.
Try using the #scoped method inside a class method of ActiveRecord::Base. This should work:
module Scopes
def self.included(base)
base.class_eval do
def self.not_older_than(interval)
scoped(:conditions => ["#{table_name}.created_at > ?", interval.ago])
end
end
end
end
ActiveRecord::Base.send(:include, Scopes)
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.
Additional useful scopes below :
module Scopes
def self.included(base)
base.class_eval do
def self.created(date_start, date_end = nil)
if date_start && date_end
scoped(:conditions => ["#{table_name}.created_at >= ? AND #{table_name}.created_at <= ?", date_start, date_end])
elsif date_start
scoped(:conditions => ["#{table_name}.created_at >= ?", date_start])
end
end
def self.updated(date_start, date_end = nil)
if date_start && date_end
scoped(:conditions => ["#{table_name}.updated_at >= ? AND #{table_name}.updated_at <= ?", date_start, date_end])
elsif date_start
scoped(:conditions => ["#{table_name}.updated_at >= ?", date_start])
end
end
end
end
end
ActiveRecord::Base.send(:include, Scopes)
Here is an updated, Rails4 compatible solution.
I am told defining global scopes like this can lead to conflicts, caveat emptor and all that, but sometimes you just need a simple scope on all your models, right?
Define a module.
# in /app/models/concerns/global_scopes.rb
module GlobalScopes
def self.included(base)
base.class_eval do
def self.in_daterange(start_date, end_date)
all.where(created_at: start_date.to_date.beginning_of_day..end_date.to_date.end_of_day)
end
end
end
end
Have the module included in ActiveRecord::Base
.
# in /config/initializers/activerecord.rb
ActiveRecord::Base.send(:include, GlobalScopes)
That's it! Notice that in Rails4 you do not have to mess with :scoped, but instead you use :all and chain your query to it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With