Возможности,
особенности и
проблемы AR::Relation
Тимофей Цветков, Evil Martians
2011
Тимофей Цветков
2kan@evilmartians.com
Мотивации слайд
Я сказал убей!
Look at them!
SO HAPPY OMG
OMG LOL
ActiveRecord — не
черный ящик
Not only Arel
• Queries are not only Arel
• AR ≠ Arel
3.x (x ≤1)
• It’s not only Arel! AR::Relation was released
• Identity Map (WHORRAAAA)
ActiveRecord
module ActiveRecord #:nodoc:
class << self # Class methods
delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to
=> :scoped
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to
=> :scoped
delegate :find_each, :find_in_batches, :to => :scoped
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where,
:preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to
=> :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
end
end
Scoped
def scoped(options = nil)
if options
scoped.apply_finder_options(options)
else
if current_scope
current_scope.clone
else
scope = relation.clone
scope.default_scoped = true
scope
end
end
end
Finder methods
• where
• having
• select
• order
• limit
• offset
All finder methods work through AR::Relation
• joins
• includes
• lock
• readonly
• from
Example.Where
def where(opts, *rest)
return self if opts.blank?
relation = clone
relation.where_values += build_where(opts, rest)
relation
end
ActiveRecord::Relation
def to_a
@records = if @readonly_value.nil? && !@klass.locking_enabled?
eager_loading? ? find_with_associations : @klass.find_by_sql
(arel.to_sql, @bind_values)
else
IdentityMap.without do
eager_loading? ? find_with_associations : @klass.find_by_sql
(arel.to_sql, @bind_values)
end
end
end
Real quering from the AR::Relation
AR::Relation problems
• merging default_scope’s with labmda (solved):
default_scope where(:name => "qwerty")
default_scope lambda { order("id DESC") }
• except (I’ve wrote a patch):
scope :xxx, order("id DESC")
scope :yyy, except(:order).order("id ASC")
@surveys = Survey.xxx.yyy
Survey Load (0.1ms) SELECT "surveys".* FROM "surveys" ORDER BY id DESC, id ASC
IdentityMap
Identity map was written by Emilio Tagua
Emilio Tagua
LEHAIM!
Without IdentityMap
user1 = User.find(1) # => #<User id: 1, name: "Josh">
user2 = User.find(1) # => #<User id: 1, name: "Josh">
user1 == user2 # => true, b/c AR::Base recognizes that
# they have the same primary key
user1.object_id == user2.object_id # => false, b/c these are two
# different in-memory objects
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
Code example from: http://edgerails.info/articles/what-s-new-in-edge-rails/2011/04/21/activerecord-identity-
map/index.html
With IdentityMap
user1 = User.find(1) # => #<User id: 1, name: "Josh">
user2 = User.find(1) # => #<User id: 1, name: "Josh">
user1 == user2 # => true
user1.object_id == user2.object_id # => true, b/c these really are
# the same in-memory objects
User Load (2.2ms) SELECT "users".* FROM "users" LIMIT 1
User with ID = 1 loaded from Identity Map
Code example from: http://edgerails.info/articles/what-s-new-in-edge-rails/2011/04/21/activerecord-identity-
map/index.html
Even more...
post = Post.find(1)
same_post = post.comments.first.post
post.object_id == same_post.object_id # => true
Code example from: http://miloops.com/post/3391477665/identity-map-and-active-record
Thread safe
• Creates for each request
• Uses Thread.current
def repository
Thread.current[:identity_map] ||= Hash.new { |h,k| h[k] = {} }
end
Interesting...
def update
@survey = Survey.find(params[:id])
@surveys = Survey.all
if @survey.update_attributes(params[:survey])
redirect_to(@survey, :notice => 'Survey was successfully updated.')
else
render :action => "edit"
end
end
def update
@survey = Survey.find(params[:id])
if @survey.update_attributes(params[:survey])
redirect_to(@survey, :notice => 'Survey was successfully updated.')
else
@surveys = Survey.all
render :action => "edit"
end
end
How to use
IdentityMap
• It will be in Rails 3.1
• To enable write in your application.rb:
config.active_record.identity_map = true
Contribute!!!112233
Троллинг

Возможности, особенности и проблемы AR::Relation

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
    Not only Arel •Queries are not only Arel • AR ≠ Arel
  • 10.
    3.x (x ≤1) •It’s not only Arel! AR::Relation was released • Identity Map (WHORRAAAA)
  • 11.
    ActiveRecord module ActiveRecord #:nodoc: class<< self # Class methods delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped end end
  • 12.
    Scoped def scoped(options =nil) if options scoped.apply_finder_options(options) else if current_scope current_scope.clone else scope = relation.clone scope.default_scoped = true scope end end end
  • 13.
    Finder methods • where •having • select • order • limit • offset All finder methods work through AR::Relation • joins • includes • lock • readonly • from
  • 14.
    Example.Where def where(opts, *rest) returnself if opts.blank? relation = clone relation.where_values += build_where(opts, rest) relation end
  • 15.
    ActiveRecord::Relation def to_a @records =if @readonly_value.nil? && !@klass.locking_enabled? eager_loading? ? find_with_associations : @klass.find_by_sql (arel.to_sql, @bind_values) else IdentityMap.without do eager_loading? ? find_with_associations : @klass.find_by_sql (arel.to_sql, @bind_values) end end end Real quering from the AR::Relation
  • 16.
    AR::Relation problems • mergingdefault_scope’s with labmda (solved): default_scope where(:name => "qwerty") default_scope lambda { order("id DESC") } • except (I’ve wrote a patch): scope :xxx, order("id DESC") scope :yyy, except(:order).order("id ASC") @surveys = Survey.xxx.yyy Survey Load (0.1ms) SELECT "surveys".* FROM "surveys" ORDER BY id DESC, id ASC
  • 17.
    IdentityMap Identity map waswritten by Emilio Tagua
  • 18.
  • 19.
    Without IdentityMap user1 =User.find(1) # => #<User id: 1, name: "Josh"> user2 = User.find(1) # => #<User id: 1, name: "Josh"> user1 == user2 # => true, b/c AR::Base recognizes that # they have the same primary key user1.object_id == user2.object_id # => false, b/c these are two # different in-memory objects User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]] CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]] Code example from: http://edgerails.info/articles/what-s-new-in-edge-rails/2011/04/21/activerecord-identity- map/index.html
  • 20.
    With IdentityMap user1 =User.find(1) # => #<User id: 1, name: "Josh"> user2 = User.find(1) # => #<User id: 1, name: "Josh"> user1 == user2 # => true user1.object_id == user2.object_id # => true, b/c these really are # the same in-memory objects User Load (2.2ms) SELECT "users".* FROM "users" LIMIT 1 User with ID = 1 loaded from Identity Map Code example from: http://edgerails.info/articles/what-s-new-in-edge-rails/2011/04/21/activerecord-identity- map/index.html
  • 21.
    Even more... post =Post.find(1) same_post = post.comments.first.post post.object_id == same_post.object_id # => true Code example from: http://miloops.com/post/3391477665/identity-map-and-active-record
  • 22.
    Thread safe • Createsfor each request • Uses Thread.current def repository Thread.current[:identity_map] ||= Hash.new { |h,k| h[k] = {} } end
  • 23.
    Interesting... def update @survey =Survey.find(params[:id]) @surveys = Survey.all if @survey.update_attributes(params[:survey]) redirect_to(@survey, :notice => 'Survey was successfully updated.') else render :action => "edit" end end def update @survey = Survey.find(params[:id]) if @survey.update_attributes(params[:survey]) redirect_to(@survey, :notice => 'Survey was successfully updated.') else @surveys = Survey.all render :action => "edit" end end
  • 24.
    How to use IdentityMap •It will be in Rails 3.1 • To enable write in your application.rb: config.active_record.identity_map = true
  • 25.
  • 26.