active-fedora 9.10.0.pre2 → 9.10.0

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/History.txt +141 -0
  4. data/lib/active_fedora/associations/association.rb +38 -12
  5. data/lib/active_fedora/associations/association_scope.rb +6 -2
  6. data/lib/active_fedora/associations/basic_contains_association.rb +2 -3
  7. data/lib/active_fedora/associations/belongs_to_association.rb +23 -4
  8. data/lib/active_fedora/associations/builder/association.rb +37 -56
  9. data/lib/active_fedora/associations/builder/belongs_to.rb +27 -1
  10. data/lib/active_fedora/associations/builder/collection_association.rb +33 -2
  11. data/lib/active_fedora/associations/builder/contains.rb +3 -3
  12. data/lib/active_fedora/associations/builder/directly_contains.rb +1 -7
  13. data/lib/active_fedora/associations/builder/directly_contains_one.rb +20 -17
  14. data/lib/active_fedora/associations/builder/has_and_belongs_to_many.rb +1 -1
  15. data/lib/active_fedora/associations/builder/has_many.rb +2 -43
  16. data/lib/active_fedora/associations/builder/indirectly_contains.rb +1 -7
  17. data/lib/active_fedora/associations/builder/property.rb +2 -13
  18. data/lib/active_fedora/associations/builder/singular_association.rb +2 -6
  19. data/lib/active_fedora/associations/builder/singular_property.rb +2 -3
  20. data/lib/active_fedora/associations/collection_association.rb +6 -14
  21. data/lib/active_fedora/associations/directly_contains_one_association.rb +1 -2
  22. data/lib/active_fedora/associations/has_and_belongs_to_many_association.rb +1 -1
  23. data/lib/active_fedora/associations/has_many_association.rb +23 -0
  24. data/lib/active_fedora/attached_files.rb +1 -1
  25. data/lib/active_fedora/core.rb +2 -1
  26. data/lib/active_fedora/errors.rb +13 -0
  27. data/lib/active_fedora/file.rb +16 -1
  28. data/lib/active_fedora/file/attributes.rb +6 -6
  29. data/lib/active_fedora/reflection.rb +200 -76
  30. data/lib/active_fedora/version.rb +1 -1
  31. data/lib/active_fedora/with_metadata/metadata_node.rb +2 -1
  32. data/lib/generators/active_fedora/config/fedora/fedora_generator.rb +4 -0
  33. data/lib/generators/active_fedora/config/fedora/templates/.fcrepo_wrapper +3 -0
  34. data/lib/generators/active_fedora/config/solr/solr_generator.rb +4 -0
  35. data/lib/generators/active_fedora/config/solr/templates/.solr_wrapper +5 -0
  36. data/spec/integration/attached_files_spec.rb +4 -4
  37. data/spec/integration/file_spec.rb +1 -1
  38. data/spec/integration/om_datastream_spec.rb +6 -6
  39. data/spec/unit/collection_proxy_spec.rb +1 -1
  40. data/spec/unit/file_spec.rb +19 -0
  41. data/spec/unit/has_and_belongs_to_many_association_spec.rb +4 -4
  42. data/spec/unit/has_many_association_spec.rb +2 -2
  43. data/spec/unit/reflection_spec.rb +2 -2
  44. metadata +6 -4
@@ -44,6 +44,19 @@ module ActiveFedora #:nodoc:
44
44
  class AssociationNotFoundError < ConfigurationError #:nodoc:
45
45
  end
46
46
 
47
+ # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
48
+ # (has_many, has_one) when there is at least 1 child associated instance.
49
+ # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
50
+ class DeleteRestrictionError < ActiveFedoraError #:nodoc:
51
+ def initialize(name = nil)
52
+ if name
53
+ super("Cannot delete record because of dependent #{name}")
54
+ else
55
+ super("Delete restriction error.")
56
+ end
57
+ end
58
+ end
59
+
47
60
  # Raised when ActiveFedora cannot find the predicate corresponding to the given property
48
61
  # in the predicate registy
49
62
  class UnregisteredPredicateError < ActiveFedoraError
@@ -63,6 +63,12 @@ module ActiveFedora
63
63
  end
64
64
  end
65
65
 
66
+ def save
67
+ super.tap do
68
+ metadata.save if metadata.changed?
69
+ end
70
+ end
71
+
66
72
  def described_by
67
73
  raise "#{self} isn't persisted yet" if new_record?
68
74
  links['describedby'].first
@@ -114,6 +120,7 @@ module ActiveFedora
114
120
  def attribute_will_change!(attr)
115
121
  if attr == 'content'
116
122
  changed_attributes['content'] = true
123
+ elsif attr == 'type'
117
124
  else
118
125
  super
119
126
  end
@@ -138,7 +145,15 @@ module ActiveFedora
138
145
  end
139
146
 
140
147
  def changed?
141
- super || content_changed?
148
+ super || content_changed? || metadata_changed?
149
+ end
150
+
151
+ def metadata_changed?
152
+ if new_record? || links['describedby'].blank?
153
+ false
154
+ else
155
+ metadata.changed?
156
+ end
142
157
  end
143
158
 
144
159
  def inspect
@@ -1,10 +1,10 @@
1
1
  module ActiveFedora::File::Attributes
2
- attr_writer :mime_type
3
-
4
2
  def mime_type
5
- @mime_type ||= fetch_mime_type
3
+ fetch_mime_type
6
4
  end
7
5
 
6
+ delegate :mime_type=, to: :metadata
7
+
8
8
  def original_name
9
9
  @original_name ||= fetch_original_name
10
10
  end
@@ -25,7 +25,7 @@ module ActiveFedora::File::Attributes
25
25
  end
26
26
 
27
27
  def dirty_size
28
- content.size if changed? && content.respond_to?(:size)
28
+ content.size if content_changed? && content.respond_to?(:size)
29
29
  end
30
30
 
31
31
  def size
@@ -68,8 +68,8 @@ module ActiveFedora::File::Attributes
68
68
  end
69
69
 
70
70
  def fetch_mime_type
71
- return default_mime_type if new_record?
72
- ldp_source.head.content_type
71
+ return default_mime_type if new_record? && metadata.mime_type.blank?
72
+ metadata.mime_type.first
73
73
  end
74
74
 
75
75
  def fetch_original_name
@@ -3,29 +3,35 @@ module ActiveFedora
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- class_attribute :reflections
7
- self.reflections = {}
6
+ class_attribute :_reflections
7
+ self._reflections = {}
8
8
  end
9
9
 
10
- module ClassMethods
11
- def create_reflection(macro, name, options, active_fedora)
10
+ def reflections
11
+ self.class.reflections
12
+ end
13
+
14
+ class << self
15
+ def create(macro, name, scope, options, active_fedora)
12
16
  klass = case macro
13
17
  when :has_many, :belongs_to, :has_and_belongs_to_many, :contains, :directly_contains, :directly_contains_one, :indirectly_contains
14
18
  AssociationReflection
15
19
  when :rdf, :singular_rdf
16
20
  RDFPropertyReflection
17
21
  end
18
- reflection = klass.new(macro, name, options, active_fedora)
19
- add_reflection name, reflection
20
-
22
+ reflection = klass.new(macro, name, scope, options, active_fedora)
23
+ add_reflection(active_fedora, name, reflection)
21
24
  reflection
22
25
  end
23
26
 
24
- def add_reflection(name, reflection)
27
+ def add_reflection(active_fedora, name, reflection)
28
+ active_fedora.clear_reflections_cache
25
29
  # FIXME: this is where the problem with association_spec is caused (key is string now)
26
- self.reflections = reflections.merge(name => reflection)
30
+ active_fedora._reflections = active_fedora._reflections.merge(name => reflection)
27
31
  end
32
+ end
28
33
 
34
+ module ClassMethods
29
35
  # Returns a hash containing all AssociationReflection objects for the current class.
30
36
  # Example:
31
37
  #
@@ -33,7 +39,47 @@ module ActiveFedora
33
39
  # Account.reflections
34
40
  #
35
41
  def reflections
36
- read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
42
+ @__reflections ||= begin
43
+ ref = {}
44
+
45
+ _reflections.each do |name, reflection|
46
+ parent_reflection = reflection.parent_reflection
47
+
48
+ if parent_reflection
49
+ parent_name = parent_reflection.name
50
+ ref[parent_name.to_s] = parent_reflection
51
+ else
52
+ ref[name] = reflection
53
+ end
54
+ end
55
+
56
+ ref
57
+ end
58
+ end
59
+
60
+ # Returns an array of AssociationReflection objects for all the
61
+ # associations in the class. If you only want to reflect on a certain
62
+ # association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
63
+ # <tt>:belongs_to</tt>) as the first parameter.
64
+ #
65
+ # Example:
66
+ #
67
+ # Account.reflect_on_all_associations # returns an array of all associations
68
+ # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
69
+ #
70
+ def reflect_on_all_associations(macro = nil)
71
+ association_reflections = reflections.dup
72
+ association_reflections.select! { |_k, reflection| reflection.macro == macro } if macro
73
+ association_reflections
74
+ end
75
+
76
+ # Returns the AssociationReflection object for the +association+ (use the symbol).
77
+ #
78
+ # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
79
+ # Invoice.reflect_on_association(:line_items).macro # returns :has_many
80
+ #
81
+ def reflect_on_association(association)
82
+ __reflect_on_association(association)
37
83
  end
38
84
 
39
85
  def outgoing_reflections
@@ -41,11 +87,11 @@ module ActiveFedora
41
87
  end
42
88
 
43
89
  def child_resource_reflections
44
- reflections.select { |_, reflection| reflection.is_a?(AssociationReflection) && reflection.macro == :contains && reflection.klass <= ActiveFedora::File }
90
+ reflect_on_all_associations(:contains).select { |_, reflection| reflection.klass <= ActiveFedora::File }
45
91
  end
46
92
 
47
93
  def contained_rdf_source_reflections
48
- reflections.select { |_, reflection| reflection.is_a?(AssociationReflection) && reflection.macro == :contains && !(reflection.klass <= ActiveFedora::File) }
94
+ reflect_on_all_associations(:contains).select { |_, reflection| !(reflection.klass <= ActiveFedora::File) }
49
95
  end
50
96
 
51
97
  # Returns the AssociationReflection object for the +association+ (use the symbol).
@@ -66,14 +112,79 @@ module ActiveFedora
66
112
  def reflect_on_all_autosave_associations
67
113
  reflections.values.select { |reflection| reflection.options[:autosave] }
68
114
  end
115
+
116
+ def clear_reflections_cache # :nodoc:
117
+ @__reflections = nil
118
+ end
69
119
  end
70
120
 
71
- class MacroReflection
121
+ # Holds all the methods that are shared between MacroReflection and ThroughReflection.
122
+ #
123
+ # AbstractReflection
124
+ # MacroReflection
125
+ # AggregateReflection
126
+ # AssociationReflection
127
+ # HasManyReflection
128
+ # HasOneReflection
129
+ # BelongsToReflection
130
+ # HasAndBelongsToManyReflection
131
+ # ThroughReflection
132
+ # PolymorphicReflection
133
+ # RuntimeReflection
134
+ class AbstractReflection # :nodoc:
135
+ def through_reflection?
136
+ false
137
+ end
138
+
139
+ # Returns a new, unsaved instance of the associated class. +attributes+ will
140
+ # be passed to the class's constructor.
141
+ def build_association(attributes, &block)
142
+ klass.new(attributes, &block)
143
+ end
144
+
145
+ # Returns the class name for the macro.
146
+ #
147
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
148
+ # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
149
+ def class_name
150
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
151
+ end
152
+
153
+ def constraints
154
+ scope_chain.flatten
155
+ end
156
+
157
+ def inverse_of
158
+ return unless inverse_name
159
+
160
+ @inverse_of ||= klass._reflect_on_association inverse_name
161
+ end
162
+
163
+ def check_validity_of_inverse!
164
+ unless polymorphic?
165
+ if has_inverse? && inverse_of.nil?
166
+ raise InverseOfAssociationNotFoundError, self
167
+ end
168
+ end
169
+ end
170
+
171
+ def alias_candidate(name)
172
+ "#{plural_name}_#{name}"
173
+ end
174
+
175
+ def chain
176
+ collect_join_chain
177
+ end
178
+ end
179
+
180
+ class MacroReflection < AbstractReflection
72
181
  # Returns the name of the macro.
73
182
  #
74
183
  # <tt>has_many :clients</tt> returns <tt>:clients</tt>
75
184
  attr_reader :name
76
185
 
186
+ attr_reader :scope
187
+
77
188
  # Returns the macro type.
78
189
  #
79
190
  # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
@@ -86,27 +197,13 @@ module ActiveFedora
86
197
 
87
198
  attr_reader :active_fedora
88
199
 
89
- # Returns the target association's class.
90
- #
91
- # class Author < ActiveRecord::Base
92
- # has_many :books
93
- # end
94
- #
95
- # Author._reflect_on_association(:books).klass
96
- # # => Book
97
- #
98
- # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
99
- # a new association object. Use +build_association+ or +create_association+
100
- # instead. This allows plugins to hook into association object creation.
101
- def klass
102
- @klass ||= class_name.constantize
103
- end
104
-
105
- def initialize(macro, name, options, active_fedora)
200
+ def initialize(macro, name, scope, options, active_fedora)
106
201
  @macro = macro
107
202
  @name = name
203
+ @scope = scope
108
204
  @options = options
109
205
  @active_fedora = active_fedora
206
+ @klass = options[:anonymous_class]
110
207
  @automatic_inverse_of = nil
111
208
  end
112
209
 
@@ -116,37 +213,26 @@ module ActiveFedora
116
213
  parent_reflection.autosave = autosave if parent_reflection
117
214
  end
118
215
 
119
- # Returns a new, unsaved instance of the associated class. +options+ will
120
- # be passed to the class's constructor.
121
- def build_association(*options, &block)
122
- klass.new(*options, &block)
123
- end
124
-
125
- # Returns the class name for the macro.
216
+ # Returns the class for the macro.
126
217
  #
127
- # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
128
- def class_name
129
- @class_name ||= options[:class_name] || derive_class_name
130
- end
131
-
132
- # Returns whether or not this association reflection is for a collection
133
- # association. Returns +true+ if the +macro+ is either +has_many+ or
134
- # +has_and_belongs_to_many+, +false+ otherwise.
135
- def collection?
136
- @collection
137
- end
138
-
139
- # Returns +true+ if +self+ is a +belongs_to+ reflection.
140
- def belongs_to?
141
- macro == :belongs_to
218
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
219
+ # <tt>has_many :clients</tt> returns the Client class
220
+ def klass
221
+ @klass ||= compute_class(class_name)
142
222
  end
143
223
 
144
- def has_many?
145
- macro == :has_many
224
+ def compute_class(name)
225
+ name.constantize
146
226
  end
147
227
 
148
- def has_and_belongs_to_many?
149
- macro == :has_and_belongs_to_many
228
+ # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
229
+ # and +other_aggregation+ has an options hash assigned to it.
230
+ def ==(other)
231
+ super ||
232
+ other.is_a?(self.class) &&
233
+ name == other.name &&
234
+ !other.options.nil? &&
235
+ active_record == other.active_record
150
236
  end
151
237
 
152
238
  private
@@ -156,22 +242,6 @@ module ActiveFedora
156
242
  class_name = class_name.singularize if collection?
157
243
  class_name
158
244
  end
159
-
160
- def derive_foreign_key
161
- if belongs_to?
162
- "#{name}_id"
163
- elsif has_and_belongs_to_many?
164
- "#{name.to_s.singularize}_ids"
165
- elsif options[:as]
166
- "#{options[:as]}_id"
167
- elsif inverse_of && inverse_of.collection?
168
- # for a has_many that is the inverse of a has_and_belongs_to_many
169
- "#{options[:inverse_of].to_s.singularize}_ids"
170
- else
171
- # for a has_many that is the inverse of a belongs_to
172
- active_fedora.name.foreign_key
173
- end
174
- end
175
245
  end
176
246
 
177
247
  # Holds all the meta-data about an association as it was specified in the
@@ -179,9 +249,13 @@ module ActiveFedora
179
249
  class AssociationReflection < MacroReflection #:nodoc:
180
250
  attr_accessor :parent_reflection # Reflection
181
251
 
182
- def initialize(macro, name, options, active_fedora)
252
+ def initialize(macro, name, scope, options, active_fedora)
183
253
  super
184
- @collection = [:has_many, :has_and_belongs_to_many, :directly_contains, :indirectly_contains].include?(macro)
254
+ @constructable = calculate_constructable(macro, options)
255
+ end
256
+
257
+ def constructable? # :nodoc:
258
+ @constructable
185
259
  end
186
260
 
187
261
  # Creates a new instance of the associated class, and immediately saves it
@@ -221,11 +295,17 @@ module ActiveFedora
221
295
 
222
296
  # A chain of reflections from this one back to the owner. For more see the explanation in
223
297
  # ThroughReflection.
224
- def chain
298
+ def collect_join_chain
225
299
  [self]
226
300
  end
301
+ alias chain collect_join_chain # todo
227
302
 
228
- alias source_macro macro
303
+ # Returns whether or not this association reflection is for a collection
304
+ # association. Returns +true+ if the +macro+ is either +has_many+ or
305
+ # +has_and_belongs_to_many+, +false+ otherwise.
306
+ def collection?
307
+ [:has_many, :has_and_belongs_to_many, :directly_contains, :indirectly_contains].include?(macro)
308
+ end
229
309
 
230
310
  def has_inverse?
231
311
  inverse_name
@@ -276,8 +356,25 @@ module ActiveFedora
276
356
  VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_and_belongs_to_many, :belongs_to].freeze
277
357
  INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key].freeze
278
358
 
359
+ # Returns +true+ if +self+ is a +belongs_to+ reflection.
360
+ def belongs_to?
361
+ macro == :belongs_to
362
+ end
363
+
364
+ def has_many?
365
+ macro == :has_many
366
+ end
367
+
368
+ def has_and_belongs_to_many?
369
+ macro == :has_and_belongs_to_many
370
+ end
371
+
279
372
  private
280
373
 
374
+ def calculate_constructable(_macro, _options)
375
+ true
376
+ end
377
+
281
378
  def inverse_name
282
379
  options.fetch(:inverse_of) do
283
380
  if @automatic_inverse_of == false
@@ -336,9 +433,30 @@ module ActiveFedora
336
433
  !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] }
337
434
  # && !reflection.scope
338
435
  end
436
+
437
+ def derive_foreign_key
438
+ if belongs_to?
439
+ "#{name}_id"
440
+ elsif has_and_belongs_to_many?
441
+ "#{name.to_s.singularize}_ids"
442
+ elsif options[:as]
443
+ "#{options[:as]}_id"
444
+ elsif inverse_of && inverse_of.collection?
445
+ # for a has_many that is the inverse of a has_and_belongs_to_many
446
+ "#{options[:inverse_of].to_s.singularize}_ids"
447
+ else
448
+ # for a has_many that is the inverse of a belongs_to
449
+ active_fedora.name.foreign_key
450
+ end
451
+ end
339
452
  end
340
453
 
341
454
  class RDFPropertyReflection < AssociationReflection
455
+ def initialize(*args)
456
+ super
457
+ active_fedora.index_config[name] = build_index_config
458
+ end
459
+
342
460
  def derive_foreign_key
343
461
  name
344
462
  end
@@ -348,6 +466,12 @@ module ActiveFedora
348
466
  class_name = class_name.singularize if collection?
349
467
  class_name
350
468
  end
469
+
470
+ private
471
+
472
+ def build_index_config
473
+ ActiveFedora::Indexing::Map::IndexObject.new(predicate_for_solr) { |index| index.as :symbol }
474
+ end
351
475
  end
352
476
  end
353
477
  end