active-fedora 9.10.0.pre2 → 9.10.0

Sign up to get free protection for your applications and to get access to all the features.
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