active-fedora 9.9.1 → 9.10.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -2
  3. data/.travis.yml +2 -6
  4. data/active-fedora.gemspec +9 -6
  5. data/lib/active_fedora.rb +31 -15
  6. data/lib/active_fedora/associations.rb +1 -1
  7. data/lib/active_fedora/associations/association.rb +6 -2
  8. data/lib/active_fedora/associations/belongs_to_association.rb +0 -10
  9. data/lib/active_fedora/associations/builder/association.rb +85 -12
  10. data/lib/active_fedora/associations/builder/belongs_to.rb +3 -1
  11. data/lib/active_fedora/associations/builder/collection_association.rb +4 -3
  12. data/lib/active_fedora/associations/builder/contains.rb +7 -2
  13. data/lib/active_fedora/associations/builder/directly_contains.rb +7 -3
  14. data/lib/active_fedora/associations/builder/directly_contains_one.rb +8 -4
  15. data/lib/active_fedora/associations/builder/has_and_belongs_to_many.rb +6 -2
  16. data/lib/active_fedora/associations/builder/has_many.rb +6 -2
  17. data/lib/active_fedora/associations/builder/indirectly_contains.rb +7 -3
  18. data/lib/active_fedora/associations/builder/property.rb +7 -2
  19. data/lib/active_fedora/associations/builder/singular_association.rb +3 -1
  20. data/lib/active_fedora/associations/builder/singular_property.rb +3 -1
  21. data/lib/active_fedora/associations/collection_association.rb +9 -5
  22. data/lib/active_fedora/associations/collection_proxy.rb +1 -1
  23. data/lib/active_fedora/associations/contained_finder.rb +1 -2
  24. data/lib/active_fedora/associations/directly_contains_one_association.rb +1 -1
  25. data/lib/active_fedora/associations/has_many_association.rb +1 -5
  26. data/lib/active_fedora/associations/rdf.rb +1 -20
  27. data/lib/active_fedora/attached_files.rb +1 -1
  28. data/lib/active_fedora/attribute_methods.rb +18 -0
  29. data/lib/active_fedora/attributes.rb +1 -1
  30. data/lib/active_fedora/autosave_association.rb +8 -12
  31. data/lib/active_fedora/base.rb +0 -2
  32. data/lib/active_fedora/caching_connection.rb +1 -1
  33. data/lib/active_fedora/default_model_mapper.rb +24 -0
  34. data/lib/active_fedora/fedora.rb +1 -1
  35. data/lib/active_fedora/file/attributes.rb +4 -5
  36. data/lib/active_fedora/identifiable.rb +5 -0
  37. data/lib/active_fedora/indexing.rb +13 -7
  38. data/lib/active_fedora/indexing_service.rb +4 -4
  39. data/lib/active_fedora/ldp_resource.rb +1 -0
  40. data/lib/active_fedora/model.rb +18 -16
  41. data/lib/active_fedora/model_classifier.rb +77 -0
  42. data/lib/active_fedora/nested_attributes.rb +145 -18
  43. data/lib/active_fedora/persistence.rb +1 -1
  44. data/lib/active_fedora/predicates.rb +3 -0
  45. data/lib/active_fedora/qualified_dublin_core_datastream.rb +1 -1
  46. data/lib/active_fedora/query_result_builder.rb +12 -28
  47. data/lib/active_fedora/querying.rb +1 -1
  48. data/lib/active_fedora/rdf/datastream_indexing.rb +1 -1
  49. data/lib/active_fedora/reflection.rb +15 -7
  50. data/lib/active_fedora/relation.rb +17 -0
  51. data/lib/active_fedora/relation/calculations.rb +1 -5
  52. data/lib/active_fedora/relation/finder_methods.rb +39 -26
  53. data/lib/active_fedora/scoping.rb +5 -0
  54. data/lib/active_fedora/scoping/default.rb +113 -0
  55. data/lib/active_fedora/scoping/named.rb +11 -3
  56. data/lib/active_fedora/simple_datastream.rb +1 -1
  57. data/lib/active_fedora/solr_hit.rb +71 -0
  58. data/lib/active_fedora/solr_instance_loader.rb +12 -36
  59. data/lib/active_fedora/solr_query_builder.rb +20 -25
  60. data/lib/active_fedora/solr_service.rb +24 -13
  61. data/lib/active_fedora/type.rb +8 -0
  62. data/lib/active_fedora/type/boolean.rb +23 -0
  63. data/lib/active_fedora/type/value.rb +118 -0
  64. data/lib/active_fedora/validations.rb +14 -5
  65. data/lib/active_fedora/version.rb +1 -1
  66. data/lib/active_fedora/versionable.rb +8 -7
  67. data/spec/config_helper.rb +0 -5
  68. data/spec/integration/associations_spec.rb +5 -5
  69. data/spec/integration/base_spec.rb +4 -4
  70. data/spec/integration/bug_spec.rb +0 -1
  71. data/spec/integration/full_featured_model_spec.rb +4 -4
  72. data/spec/integration/has_and_belongs_to_many_associations_spec.rb +1 -1
  73. data/spec/integration/has_many_associations_spec.rb +30 -1
  74. data/spec/integration/indirect_container_spec.rb +1 -1
  75. data/spec/integration/nested_attribute_spec.rb +6 -0
  76. data/spec/integration/ntriples_datastream_spec.rb +4 -4
  77. data/spec/integration/om_datastream_spec.rb +1 -1
  78. data/spec/integration/relation_delegation_spec.rb +1 -1
  79. data/spec/integration/scoped_query_spec.rb +12 -12
  80. data/spec/integration/solr_hit_spec.rb +52 -0
  81. data/spec/samples/hydra-mods_article_datastream.rb +2 -2
  82. data/spec/spec_helper.rb +5 -9
  83. data/spec/unit/active_fedora_spec.rb +0 -26
  84. data/spec/unit/base_spec.rb +20 -0
  85. data/spec/unit/builder/has_and_belongs_to_many_spec.rb +2 -3
  86. data/spec/unit/callback_spec.rb +3 -8
  87. data/spec/unit/default_model_mapper_spec.rb +39 -0
  88. data/spec/unit/finder_methods_spec.rb +30 -6
  89. data/spec/unit/has_many_association_spec.rb +23 -1
  90. data/spec/unit/indexing_spec.rb +17 -3
  91. data/spec/unit/model_classifier_spec.rb +49 -0
  92. data/spec/unit/model_spec.rb +0 -9
  93. data/spec/unit/ntriples_datastream_spec.rb +16 -16
  94. data/spec/unit/om_datastream_spec.rb +7 -7
  95. data/spec/unit/qualified_dublin_core_datastream_spec.rb +1 -1
  96. data/spec/unit/query_result_builder_spec.rb +4 -10
  97. data/spec/unit/query_spec.rb +28 -28
  98. data/spec/unit/rdf/indexing_service_spec.rb +16 -16
  99. data/spec/unit/scoping_spec.rb +67 -0
  100. data/spec/unit/simple_datastream_spec.rb +2 -2
  101. data/spec/unit/solr_config_options_spec.rb +29 -32
  102. data/spec/unit/solr_hit_spec.rb +58 -0
  103. data/spec/unit/solr_query_builder_spec.rb +9 -1
  104. data/spec/unit/solr_service_spec.rb +19 -3
  105. metadata +73 -17
  106. data/spec/support/freeze_mocks.rb +0 -12
@@ -1,50 +1,34 @@
1
1
  module ActiveFedora
2
2
  module QueryResultBuilder
3
3
  def self.lazy_reify_solr_results(solr_results, opts = {})
4
- Enumerator.new do |yielder|
5
- solr_results.each do |hit|
6
- yielder.yield(reify_solr_result(hit, opts))
7
- end
4
+ return to_enum(:lazy_reify_solr_results, solr_results, opts) unless block_given?
5
+
6
+ solr_results.each do |hit|
7
+ yield ActiveFedora::SolrHit.for(hit).reify(opts)
8
8
  end
9
9
  end
10
10
 
11
11
  def self.reify_solr_results(solr_results, opts = {})
12
- solr_results.collect { |hit| reify_solr_result(hit, opts) }
12
+ lazy_reify_solr_results(solr_results, opts).to_a
13
13
  end
14
14
 
15
15
  def self.reify_solr_result(hit, _opts = {})
16
- klass = class_from_solr_document(hit)
17
- klass.find(hit[SOLR_DOCUMENT_ID], cast: true)
16
+ Deprecation.warn(ActiveFedora::Base, 'ActiveFedora::QueryResultBuilder.reify_solr_result is deprecated and will be removed in ActiveFedora 10.0; call #reify on the SolrHit instead.')
17
+ ActiveFedora::SolrHit.for(hit).reify
18
18
  end
19
19
 
20
20
  # Returns all possible classes for the solr object
21
21
  def self.classes_from_solr_document(hit, _opts = {})
22
- classes = []
23
-
24
- hit[HAS_MODEL_SOLR_FIELD].each { |value| classes << Model.from_class_uri(value) }
25
-
26
- classes.compact
22
+ Deprecation.warn(ActiveFedora::Base, 'ActiveFedora::QueryResultBuilder.classes_from_solr_document is deprecated and will be removed in ActiveFedora 10.0; call #models on the SolrHit instead.')
23
+ ActiveFedora::SolrHit.for(hit).models
27
24
  end
28
25
 
29
26
  # Returns the best singular class for the solr object
30
27
  def self.class_from_solr_document(hit, opts = {})
31
- # Set the default starting point to the class specified, if available.
32
- best_model_match = Model.from_class_uri(opts[:class]) unless opts[:class].nil?
33
- Array(hit[HAS_MODEL_SOLR_FIELD]).each do |value|
34
- model_value = Model.from_class_uri(value)
35
- next unless model_value
36
-
37
- # Set as the first model in case opts[:class] was nil
38
- best_model_match ||= model_value
39
-
40
- # If there is an inheritance structure, use the most specific case.
41
- best_model_match = model_value if best_model_match > model_value
42
- end
43
-
44
- ActiveFedora::Base.logger.warn "Could not find a model for #{hit['id']}, defaulting to ActiveFedora::Base" if ActiveFedora::Base.logger && !best_model_match
45
- best_model_match || ActiveFedora::Base
28
+ Deprecation.warn(ActiveFedora::Base, 'ActiveFedora::QueryResultBuilder.class_from_solr_document is deprecated and will be removed in ActiveFedora 10.0; call #model on the SolrHit instead.')
29
+ ActiveFedora::SolrHit.for(hit).model(opts)
46
30
  end
47
31
 
48
- HAS_MODEL_SOLR_FIELD = SolrQueryBuilder.solr_name("has_model", :symbol).freeze
32
+ HAS_MODEL_SOLR_FIELD = ActiveFedora.index_field_mapper.solr_name("has_model", :symbol).freeze
49
33
  end
50
34
  end
@@ -1,7 +1,7 @@
1
1
  module ActiveFedora
2
2
  module Querying
3
3
  delegate :find, :first, :exists?, :where, :limit, :offset, :order, :delete_all,
4
- :destroy_all, :count, :last, :find_with_conditions, :find_in_batches, :find_each, to: :all
4
+ :destroy_all, :count, :last, :find_with_conditions, :find_in_batches, :search_with_conditions, :search_in_batches, :search_by_id, :find_each, to: :all
5
5
 
6
6
  def self.extended(base)
7
7
  base.class_attribute :solr_query_handler
@@ -13,7 +13,7 @@ module ActiveFedora::RDF
13
13
  config = self.class.config_for_term_or_uri(field)
14
14
  return nil unless config && config.behaviors # punt on index names for deep nodes!
15
15
  config.behaviors.each do |behavior|
16
- result = ActiveFedora::SolrQueryBuilder.solr_name(apply_prefix(field, file_path), behavior, type: config.type)
16
+ result = ActiveFedora.index_field_mapper.solr_name(apply_prefix(field, file_path), behavior, type: config.type)
17
17
  return result if Solrizer::DefaultDescriptors.send(behavior).evaluate_suffix(:text).stored?
18
18
  end
19
19
  raise RuntimeError "no stored fields were found"
@@ -50,10 +50,10 @@ module ActiveFedora
50
50
 
51
51
  # Returns the AssociationReflection object for the +association+ (use the symbol).
52
52
  #
53
- # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
54
- # Invoice.reflect_on_association(:line_items).macro # returns :has_many
53
+ # Account._reflect_on_association(:owner) # returns the owner AssociationReflection
54
+ # Invoice._reflect_on_association(:line_items).macro # returns :has_many
55
55
  #
56
- def reflect_on_association(association)
56
+ def _reflect_on_association(association)
57
57
  val = reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
58
58
  unless val
59
59
  # When a has_many is paired with a has_and_belongs_to_many the assocation will have a plural name
@@ -92,7 +92,7 @@ module ActiveFedora
92
92
  # has_many :books
93
93
  # end
94
94
  #
95
- # Author.reflect_on_association(:books).klass
95
+ # Author._reflect_on_association(:books).klass
96
96
  # # => Book
97
97
  #
98
98
  # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
@@ -110,6 +110,12 @@ module ActiveFedora
110
110
  @automatic_inverse_of = nil
111
111
  end
112
112
 
113
+ def autosave=(autosave)
114
+ @options[:autosave] = autosave
115
+ parent_reflection = self.parent_reflection
116
+ parent_reflection.autosave = autosave if parent_reflection
117
+ end
118
+
113
119
  # Returns a new, unsaved instance of the associated class. +options+ will
114
120
  # be passed to the class's constructor.
115
121
  def build_association(*options, &block)
@@ -171,6 +177,8 @@ module ActiveFedora
171
177
  # Holds all the meta-data about an association as it was specified in the
172
178
  # Active Record class.
173
179
  class AssociationReflection < MacroReflection #:nodoc:
180
+ attr_accessor :parent_reflection # Reflection
181
+
174
182
  def initialize(macro, name, options, active_fedora)
175
183
  super
176
184
  @collection = [:has_many, :has_and_belongs_to_many, :directly_contains, :indirectly_contains].include?(macro)
@@ -198,7 +206,7 @@ module ActiveFedora
198
206
 
199
207
  def solr_key
200
208
  @solr_key ||= begin
201
- ActiveFedora::SolrQueryBuilder.solr_name(predicate_for_solr, :symbol)
209
+ ActiveFedora.index_field_mapper.solr_name(predicate_for_solr, :symbol)
202
210
  end
203
211
  end
204
212
 
@@ -226,7 +234,7 @@ module ActiveFedora
226
234
  def inverse_of
227
235
  return unless inverse_name
228
236
 
229
- @inverse_of ||= klass.reflect_on_association inverse_name
237
+ @inverse_of ||= klass._reflect_on_association inverse_name
230
238
  end
231
239
 
232
240
  # Returns whether or not the association should be validated as part of
@@ -299,7 +307,7 @@ module ActiveFedora
299
307
  inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_fedora.name.demodulize).to_sym
300
308
 
301
309
  begin
302
- reflection = klass.reflect_on_association(inverse_name)
310
+ reflection = klass._reflect_on_association(inverse_name)
303
311
  rescue NameError
304
312
  # Give up: we couldn't compute the klass type so we won't be able
305
313
  # to find any associations either.
@@ -62,6 +62,23 @@ module ActiveFedora
62
62
  @records
63
63
  end
64
64
 
65
+ # Scope all queries to the current scope.
66
+ #
67
+ # Comment.where(post_id: 1).scoping do
68
+ # Comment.first
69
+ # end
70
+ # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
71
+ #
72
+ # Please check unscoped if you want to remove all previous scopes (including
73
+ # the default_scope) during the execution of a block.
74
+ def scoping
75
+ previous = klass.current_scope
76
+ klass.current_scope = self
77
+ yield
78
+ ensure
79
+ klass.current_scope = previous
80
+ end
81
+
65
82
  def ==(other)
66
83
  case other
67
84
  when Relation
@@ -8,11 +8,7 @@ module ActiveFedora
8
8
  opts[:rows] = limit_value if limit_value
9
9
  opts[:sort] = order_values if order_values
10
10
 
11
- calculate :count, where_values, opts
12
- end
13
-
14
- def calculate(_calculation, conditions, _opts = {})
15
- SolrService.query(create_query(conditions), raw: true, rows: 0).fetch('response'.freeze)['numFound'.freeze]
11
+ SolrService.count(create_query(where_values))
16
12
  end
17
13
  end
18
14
  end
@@ -87,7 +87,7 @@ module ActiveFedora
87
87
  return false unless conditions
88
88
  case conditions
89
89
  when Hash
90
- find_with_conditions(conditions, rows: 1).present?
90
+ search_with_conditions(conditions, rows: 1).present?
91
91
  when String
92
92
  find(conditions).present?
93
93
  else
@@ -102,15 +102,35 @@ module ActiveFedora
102
102
  # hash representing the query part of an solr statement. If a hash is
103
103
  # provided, this method will generate conditions based simple equality
104
104
  # combined using the boolean AND operator.
105
- # @param[Hash] options
105
+ # @param [Hash] options
106
106
  # @option opts [Array] :sort a list of fields to sort by
107
107
  # @option opts [Array] :rows number of rows to return
108
- def find_with_conditions(conditions, opts = {})
108
+ def search_with_conditions(conditions, opts = {})
109
109
  # set default sort to created date ascending
110
110
  opts[:sort] = @klass.default_sort_params unless opts.include?(:sort)
111
111
  SolrService.query(create_query(conditions), opts)
112
112
  end
113
113
 
114
+ # Returns a single solr hit matching the given id
115
+ # @param [String] id document id
116
+ # @param [Hash] opts
117
+ def search_by_id(id, opts = {})
118
+ opts[:rows] = 1
119
+ result = search_with_conditions({ id: id }, opts)
120
+
121
+ if result.empty?
122
+ raise ActiveFedora::ObjectNotFoundError, "Object #{id} not found in solr"
123
+ end
124
+
125
+ result.first
126
+ end
127
+
128
+ # @deprecated
129
+ def find_with_conditions(*args)
130
+ Deprecation.warn(ActiveFedora::Base, '.find_with_conditions is deprecated and will be removed in active-fedora 10.0; use .search_with_conditions instead')
131
+ search_with_conditions(*args)
132
+ end
133
+
114
134
  # Yields the found ActiveFedora::Base object to the passed block
115
135
  #
116
136
  # @param [Hash] conditions the conditions for the solr search to match
@@ -118,12 +138,12 @@ module ActiveFedora
118
138
  # @option opts [Boolean] :cast (true) when true, examine the model and cast it to the first known cModel
119
139
  def find_each(conditions = {}, opts = {})
120
140
  cast = opts.delete(:cast)
121
- find_in_batches(conditions, opts.merge(fl: SOLR_DOCUMENT_ID)) do |group|
141
+ search_in_batches(conditions, opts.merge(fl: ActiveFedora.id_field)) do |group|
122
142
  group.each do |hit|
123
143
  begin
124
- yield(load_from_fedora(hit[SOLR_DOCUMENT_ID], cast))
144
+ yield(load_from_fedora(hit[ActiveFedora.id_field], cast))
125
145
  rescue Ldp::Gone
126
- ActiveFedora::Base.logger.error "Although #{hit[SOLR_DOCUMENT_ID]} was found in Solr, it doesn't seem to exist in Fedora. The index is out of synch." if ActiveFedora::Base.logger
146
+ ActiveFedora::Base.logger.error "Although #{hit[ActiveFedora.id_field]} was found in Solr, it doesn't seem to exist in Fedora. The index is out of synch." if ActiveFedora::Base.logger
127
147
  end
128
148
  end
129
149
  end
@@ -140,11 +160,10 @@ module ActiveFedora
140
160
  # @option opts [Array] :rows number of rows to return
141
161
  #
142
162
  # @example
143
- # Person.find_in_batches('age_t'=>'21', {:batch_size=>50}) do |group|
163
+ # Person.search_in_batches('age_t'=>'21', {:batch_size=>50}) do |group|
144
164
  # group.each { |person| puts person['name_t'] }
145
165
  # end
146
-
147
- def find_in_batches(conditions, opts = {})
166
+ def search_in_batches(conditions, opts = {})
148
167
  opts[:q] = create_query(conditions)
149
168
  opts[:qt] = @klass.solr_query_handler
150
169
  # set default sort to created date ascending
@@ -163,6 +182,12 @@ module ActiveFedora
163
182
  end
164
183
  end
165
184
 
185
+ # @deprecated
186
+ def find_in_batches(*args)
187
+ Deprecation.warn(ActiveFedora::Base, '.find_in_batches is deprecated and will be removed in active-fedora 10.0; use .search_in_batches instead')
188
+ search_in_batches(*args)
189
+ end
190
+
166
191
  # Retrieve the Fedora object with the given id, explore the returned object
167
192
  # Raises a ObjectNotFoundError if the object is not found.
168
193
  # @param [String] id of the object to load
@@ -174,7 +199,7 @@ module ActiveFedora
174
199
  if where_values.empty?
175
200
  load_from_fedora(id, cast)
176
201
  else
177
- conditions = where_values + [ActiveFedora::SolrQueryBuilder.raw_query(SOLR_DOCUMENT_ID, id)]
202
+ conditions = where_values + [ActiveFedora::SolrQueryBuilder.construct_query(ActiveFedora.id_field => id)]
178
203
  query = conditions.join(" AND ".freeze)
179
204
  to_enum(:find_each, query, {}).to_a.first
180
205
  end
@@ -193,8 +218,7 @@ module ActiveFedora
193
218
  if @klass == ActiveFedora::Base && cast == false
194
219
  ActiveFedora::Base
195
220
  else
196
- # The true class may be a subclass of @klass, so always use from_class_uri
197
- resource_class = Model.from_class_uri(has_model_value(resource)) || ActiveFedora::Base
221
+ resource_class = has_model_value(resource)
198
222
  unless equivalent_class?(resource_class)
199
223
  raise ActiveFedora::ActiveFedoraError, "Model mismatch. Expected #{@klass}. Got: #{resource_class}"
200
224
  end
@@ -203,18 +227,7 @@ module ActiveFedora
203
227
  end
204
228
 
205
229
  def has_model_value(resource)
206
- best_model_match = nil
207
-
208
- resource.graph.query([nil, ActiveFedora::RDF::Fcrepo::Model.hasModel, nil]).each do |rg|
209
- model_value = Model.from_class_uri(rg.object.to_s)
210
- next unless model_value
211
- best_model_match ||= model_value
212
-
213
- # If there is an inheritance structure, use the most specific case.
214
- best_model_match = model_value if best_model_match > model_value
215
- end
216
-
217
- best_model_match.to_s
230
+ ActiveFedora.model_mapper.classifier(resource).best_model
218
231
  end
219
232
 
220
233
  def equivalent_class?(other_class)
@@ -276,9 +289,9 @@ module ActiveFedora
276
289
  def field_name_for(key)
277
290
  if @klass.delegated_attributes.key?(key)
278
291
  # TODO: Check to see if `key' is a possible solr field for this class, if it isn't try :searchable instead
279
- ActiveFedora::SolrQueryBuilder.solr_name(key, :stored_searchable, type: :string)
292
+ ActiveFedora.index_field_mapper.solr_name(key, :stored_searchable, type: :string)
280
293
  elsif key == :id
281
- SOLR_DOCUMENT_ID
294
+ ActiveFedora.id_field
282
295
  else
283
296
  key.to_s
284
297
  end
@@ -14,6 +14,11 @@ module ActiveFedora
14
14
  def current_scope=(scope) #:nodoc:
15
15
  Thread.current["#{self}_current_scope"] = scope
16
16
  end
17
+
18
+ # Are there attributes associated with this scope?
19
+ def scope_attributes? # :nodoc:
20
+ current_scope
21
+ end
17
22
  end
18
23
  end
19
24
  end
@@ -3,6 +3,14 @@ module ActiveFedora
3
3
  module Default
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ included do
7
+ class_attribute :default_scopes, instance_writer: false, instance_predicate: false
8
+ class_attribute :default_scope_override, instance_writer: false, instance_predicate: false
9
+
10
+ self.default_scopes = []
11
+ self.default_scope_override = nil
12
+ end
13
+
6
14
  module ClassMethods
7
15
  # Returns a scope for the model without the +default_scope+.
8
16
  #
@@ -32,6 +40,111 @@ module ActiveFedora
32
40
  def unscoped
33
41
  block_given? ? relation.scoping { yield } : relation
34
42
  end
43
+
44
+ # Are there attributes associated with this scope?
45
+ def scope_attributes? # :nodoc:
46
+ super || default_scopes.any? || respond_to?(:default_scope)
47
+ end
48
+
49
+ protected
50
+
51
+ # Use this macro in your model to set a default scope for all operations on
52
+ # the model.
53
+ #
54
+ # class Article < ActiveRecord::Base
55
+ # default_scope { where(published: true) }
56
+ # end
57
+ #
58
+ # Article.all # => SELECT * FROM articles WHERE published = true
59
+ #
60
+ # The #default_scope is also applied while creating/building a record.
61
+ # It is not applied while updating a record.
62
+ #
63
+ # Article.new.published # => true
64
+ # Article.create.published # => true
65
+ #
66
+ # (You can also pass any object which responds to +call+ to the
67
+ # +default_scope+ macro, and it will be called when building the
68
+ # default scope.)
69
+ #
70
+ # If you use multiple #default_scope declarations in your model then
71
+ # they will be merged together:
72
+ #
73
+ # class Article < ActiveRecord::Base
74
+ # default_scope { where(published: true) }
75
+ # default_scope { where(rating: 'G') }
76
+ # end
77
+ #
78
+ # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
79
+ #
80
+ # This is also the case with inheritance and module includes where the
81
+ # parent or module defines a #default_scope and the child or including
82
+ # class defines a second one.
83
+ #
84
+ # If you need to do more complex things with a default scope, you can
85
+ # alternatively define it as a class method:
86
+ #
87
+ # class Article < ActiveRecord::Base
88
+ # def self.default_scope
89
+ # # Should return a scope, you can call 'super' here etc.
90
+ # end
91
+ # end
92
+ def default_scope(scope = nil)
93
+ scope = Proc.new if block_given?
94
+
95
+ if scope.is_a?(Relation) || !scope.respond_to?(:call)
96
+ raise ArgumentError,
97
+ "Support for calling #default_scope without a block is removed. For example instead " \
98
+ "of `default_scope where(color: 'red')`, please use " \
99
+ "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
100
+ "self.default_scope.)"
101
+ end
102
+
103
+ self.default_scopes += [scope]
104
+ end
105
+
106
+ def build_default_scope(base_rel = nil) # :nodoc:
107
+ return if abstract_class?
108
+
109
+ if default_scope_override.nil?
110
+ self.default_scope_override = !Base.is_a?(method(:default_scope).owner)
111
+ end
112
+
113
+ if default_scope_override
114
+ # The user has defined their own default scope method, so call that
115
+ evaluate_default_scope { default_scope }
116
+ elsif default_scopes.any?
117
+ base_rel ||= relation
118
+ evaluate_default_scope do
119
+ default_scopes.inject(base_rel) do |default_scope, scope|
120
+ scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
121
+ default_scope.merge(base_rel.instance_exec(&scope))
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ def ignore_default_scope? # :nodoc:
128
+ Thread.current["#{self}_ignore_default_scope"]
129
+ end
130
+
131
+ def ignore_default_scope=(ignore) # :nodoc:
132
+ Thread.current["#{self}_ignore_default_scope"] = ignore
133
+ end
134
+
135
+ # The ignore_default_scope flag is used to prevent an infinite recursion
136
+ # situation where a default scope references a scope which has a default
137
+ # scope which references a scope...
138
+ def evaluate_default_scope # :nodoc:
139
+ return if ignore_default_scope?
140
+
141
+ begin
142
+ self.ignore_default_scope = true
143
+ yield
144
+ ensure
145
+ self.ignore_default_scope = false
146
+ end
147
+ end
35
148
  end
36
149
  end
37
150
  end