active-fedora 9.9.1 → 9.10.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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