/activerecord/lib/active_record/scoping/named.rb

https://github.com/benolee/rails · Ruby · 158 lines · 40 code · 8 blank · 110 comment · 6 complexity · 6ba658bf201b1839536b377320f5dafb MD5 · raw file

  1. require 'active_support/core_ext/array'
  2. require 'active_support/core_ext/hash/except'
  3. require 'active_support/core_ext/kernel/singleton_class'
  4. module ActiveRecord
  5. # = Active Record \Named \Scopes
  6. module Scoping
  7. module Named
  8. extend ActiveSupport::Concern
  9. module ClassMethods
  10. # Returns an <tt>ActiveRecord::Relation</tt> scope object.
  11. #
  12. # posts = Post.all
  13. # posts.size # Fires "select count(*) from posts" and returns the count
  14. # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
  15. #
  16. # fruits = Fruit.all
  17. # fruits = fruits.where(color: 'red') if options[:red_only]
  18. # fruits = fruits.limit(10) if limited?
  19. #
  20. # You can define a scope that applies to all finders using
  21. # <tt>ActiveRecord::Base.default_scope</tt>.
  22. def all
  23. if current_scope
  24. current_scope.clone
  25. else
  26. default_scoped
  27. end
  28. end
  29. def default_scoped # :nodoc:
  30. relation.merge(build_default_scope)
  31. end
  32. # Collects attributes from scopes that should be applied when creating
  33. # an AR instance for the particular class this is called on.
  34. def scope_attributes # :nodoc:
  35. all.scope_for_create
  36. end
  37. # Are there default attributes associated with this scope?
  38. def scope_attributes? # :nodoc:
  39. current_scope || default_scopes.any?
  40. end
  41. # Adds a class method for retrieving and querying objects. A \scope
  42. # represents a narrowing of a database query, such as
  43. # <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
  44. #
  45. # class Shirt < ActiveRecord::Base
  46. # scope :red, -> { where(color: 'red') }
  47. # scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
  48. # end
  49. #
  50. # The above calls to +scope+ define class methods <tt>Shirt.red</tt> and
  51. # <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
  52. # represents the query <tt>Shirt.where(color: 'red')</tt>.
  53. #
  54. # You should always pass a callable object to the scopes defined
  55. # with +scope+. This ensures that the scope is re-evaluated each
  56. # time it is called.
  57. #
  58. # Note that this is simply 'syntactic sugar' for defining an actual
  59. # class method:
  60. #
  61. # class Shirt < ActiveRecord::Base
  62. # def self.red
  63. # where(color: 'red')
  64. # end
  65. # end
  66. #
  67. # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
  68. # <tt>Shirt.red</tt> is not an Array; it resembles the association object
  69. # constructed by a +has_many+ declaration. For instance, you can invoke
  70. # <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
  71. # <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
  72. # association objects, named \scopes act like an Array, implementing
  73. # Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
  74. # and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
  75. # <tt>Shirt.red</tt> really was an Array.
  76. #
  77. # These named \scopes are composable. For instance,
  78. # <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are
  79. # both red and dry clean only. Nested finds and calculations also work
  80. # with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
  81. # returns the number of garments for which these criteria obtain.
  82. # Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
  83. #
  84. # All scopes are available as class methods on the ActiveRecord::Base
  85. # descendant upon which the \scopes were defined. But they are also
  86. # available to +has_many+ associations. If,
  87. #
  88. # class Person < ActiveRecord::Base
  89. # has_many :shirts
  90. # end
  91. #
  92. # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
  93. # Elton's red, dry clean only shirts.
  94. #
  95. # \Named scopes can also have extensions, just as with +has_many+
  96. # declarations:
  97. #
  98. # class Shirt < ActiveRecord::Base
  99. # scope :red, -> { where(color: 'red') } do
  100. # def dom_id
  101. # 'red_shirts'
  102. # end
  103. # end
  104. # end
  105. #
  106. # Scopes can also be used while creating/building a record.
  107. #
  108. # class Article < ActiveRecord::Base
  109. # scope :published, -> { where(published: true) }
  110. # end
  111. #
  112. # Article.published.new.published # => true
  113. # Article.published.create.published # => true
  114. #
  115. # \Class methods on your model are automatically available
  116. # on scopes. Assuming the following setup:
  117. #
  118. # class Article < ActiveRecord::Base
  119. # scope :published, -> { where(published: true) }
  120. # scope :featured, -> { where(featured: true) }
  121. #
  122. # def self.latest_article
  123. # order('published_at desc').first
  124. # end
  125. #
  126. # def self.titles
  127. # pluck(:title)
  128. # end
  129. # end
  130. #
  131. # We are able to call the methods like this:
  132. #
  133. # Article.published.featured.latest_article
  134. # Article.featured.titles
  135. def scope(name, body, &block)
  136. extension = Module.new(&block) if block
  137. singleton_class.send(:define_method, name) do |*args|
  138. if body.respond_to?(:call)
  139. scope = all.scoping { body.call(*args) }
  140. scope = scope.extending(extension) if extension
  141. else
  142. scope = body
  143. end
  144. scope || all
  145. end
  146. end
  147. end
  148. end
  149. end
  150. end