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
@@ -4,10 +4,36 @@ module ActiveFedora::Associations::Builder
4
4
  :belongs_to
5
5
  end
6
6
 
7
- def validate_options
7
+ def self.valid_options(options)
8
+ super + [:optional]
9
+ end
10
+
11
+ def self.valid_dependent_options
12
+ [:destroy, :delete]
13
+ end
14
+
15
+ def self.validate_options(options)
8
16
  super
9
17
  raise "You must specify a predicate for #{name}" unless options[:predicate]
10
18
  raise ArgumentError, "Predicate must be a kind of RDF::URI" unless options[:predicate].is_a?(RDF::URI)
11
19
  end
20
+
21
+ def self.define_validations(model, reflection)
22
+ if reflection.options.key?(:required)
23
+ reflection.options[:optional] = !reflection.options.delete(:required)
24
+ end
25
+
26
+ required = if reflection.options[:optional].nil?
27
+ model.belongs_to_required_by_default
28
+ else
29
+ !reflection.options[:optional]
30
+ end
31
+
32
+ super
33
+
34
+ if required
35
+ model.validates_presence_of reflection.name, message: :required
36
+ end
37
+ end
12
38
  end
13
39
  end
@@ -8,18 +8,49 @@ module ActiveFedora::Associations::Builder
8
8
  end
9
9
 
10
10
  def self.define_callbacks(model, reflection)
11
+ super
11
12
  name = reflection.name
12
13
  options = reflection.options
13
- super
14
14
  CALLBACKS.each { |callback_name| define_callback(model, callback_name, name, options) }
15
15
  end
16
16
 
17
+ def self.define_extensions(model, name)
18
+ if block_given?
19
+ extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
20
+ extension = Module.new(&Proc.new)
21
+ model.parent.const_set(extension_module_name, extension)
22
+ end
23
+ end
24
+
17
25
  def self.define_callback(model, callback_name, name, options)
18
26
  full_callback_name = "#{callback_name}_for_#{name}"
19
27
 
20
28
  # TODO : why do i need method_defined? I think its because of the inheritance chain
21
29
  model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
22
- model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
30
+
31
+ callbacks = Array(options[callback_name.to_sym]).map do |callback|
32
+ case callback
33
+ when Symbol
34
+ ->(_method, owner, record) { owner.send(callback, record) }
35
+ when Proc
36
+ ->(_method, owner, record) { callback.call(owner, record) }
37
+ else
38
+ ->(method, owner, record) { callback.send(method, owner, record) }
39
+ end
40
+ end
41
+ model.send("#{full_callback_name}=", callbacks)
42
+ end
43
+
44
+ def self.wrap_scope(scope, mod)
45
+ if scope
46
+ if scope.arity > 0
47
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
48
+ else
49
+ proc { instance_exec(&scope).extending(mod) }
50
+ end
51
+ else
52
+ proc { extending(mod) }
53
+ end
23
54
  end
24
55
  end
25
56
  end
@@ -8,12 +8,12 @@ module ActiveFedora::Associations::Builder
8
8
  super + [:autocreate, :block]
9
9
  end
10
10
 
11
- def initialize(model, name, options)
12
- super
11
+ def self.create_reflection(model, name, scope, options, extension = nil)
13
12
  options[:class_name] = 'ActiveFedora::File' if options[:class_name].blank?
13
+ super(model, name, scope, options, extension)
14
14
  end
15
15
 
16
- def validate_options
16
+ def self.validate_options(options)
17
17
  super
18
18
  return unless options[:class_name] && !options[:class_name].is_a?(String)
19
19
  raise ArgumentError, ":class_name must be a string for contains '#{name}'"
@@ -8,13 +8,7 @@ module ActiveFedora::Associations::Builder
8
8
  super + [:has_member_relation, :is_member_of_relation] - [:predicate]
9
9
  end
10
10
 
11
- def build
12
- reflection = super
13
- configure_dependency
14
- reflection
15
- end
16
-
17
- def validate_options
11
+ def self.validate_options(options)
18
12
  super
19
13
  if !options[:has_member_relation] && !options[:is_member_of_relation]
20
14
  raise ArgumentError, "You must specify a :has_member_relation or :is_member_of_relation predicate for #{name}"
@@ -8,11 +8,18 @@ module ActiveFedora::Associations::Builder
8
8
  super + [:has_member_relation, :is_member_of_relation, :type, :through] - [:predicate]
9
9
  end
10
10
 
11
- def validate_options
12
- raise ArgumentError, "you must specify a :through option on #{name}. #{name} will use the container from that directly_contains association." unless options[:through]
13
- inherit_options_from_association(options[:through]) if options[:through]
11
+ def self.create_reflection(model, name, scope, options, extension = nil)
12
+ if options[:through]
13
+ inherit_options_from_association(model, options, options[:through])
14
+ else
15
+ raise ArgumentError, "you must specify a :through option on #{name}. #{name} will use the container from that directly_contains association."
16
+ end
17
+
14
18
  super
19
+ end
15
20
 
21
+ def self.validate_options(options)
22
+ super
16
23
  if options[:class_name] == "ActiveFedora::File"
17
24
  raise ArgumentError, "You cannot set :class_name of #{name} to ActiveFedora::File because directly_contains_one needs to assert and read RDF.type assertions, which is not supported by ActiveFedora::File. To make Files support RDF.type assertions, define a subclass of ActiveFedora::File and make it `include ActiveFedora::WithMetadata`. Otherwise, all subclasses of ActiveFedora::Base support RDF.type assertions."
18
25
  elsif !options[:has_member_relation] && !options[:is_member_of_relation]
@@ -25,20 +32,16 @@ module ActiveFedora::Associations::Builder
25
32
  raise ArgumentError, "You must specify a Type and it must be a kind of RDF::URI"
26
33
  end
27
34
 
28
- private
29
-
30
- # Inherits :has_member_relation from the association corresponding to association_name
31
- # @param [Symbol] association_name of the association to inherit from
32
- def inherit_options_from_association(association_name)
33
- associated_through_reflection = lookup_reflection(association_name)
34
- raise ArgumentError, "You specified `:through => #{@reflection.options[:through]}` on the #{name} associaiton but #{model} does not actually have a #{@reflection.options[:through]}` association" if associated_through_reflection.nil? || !associated_through_reflection.name
35
- raise ArgumentError, "You must specify a directly_contains association as the :through option on #{name}. You provided a #{associated_through_reflection.macro}" unless associated_through_reflection.macro == :directly_contains
36
- options[:has_member_relation] = associated_through_reflection.options[:has_member_relation] unless options[:has_member_relation]
37
- options[:class_name] = associated_through_reflection.options[:class_name] unless options[:class_name] && options[:class_name] != "ActiveFedora::File"
38
- end
35
+ # Inherits :has_member_relation from the association corresponding to association_name
36
+ # @param [Symbol] association_name of the association to inherit from
37
+ def self.inherit_options_from_association(model, options, association_name)
38
+ associated_through_reflection = model._reflect_on_association(association_name)
39
+ raise ArgumentError, "You specified `:through => #{@reflection.options[:through]}` on the #{name} associaiton but #{model} does not actually have a #{@reflection.options[:through]}` association" if associated_through_reflection.nil? || !associated_through_reflection.name
40
+ raise ArgumentError, "You must specify a directly_contains association as the :through option on #{name}. You provided a #{associated_through_reflection.macro}" unless associated_through_reflection.macro == :directly_contains
41
+ options[:has_member_relation] = associated_through_reflection.options[:has_member_relation] unless options[:has_member_relation]
42
+ options[:class_name] = associated_through_reflection.options[:class_name] unless options[:class_name] && options[:class_name] != "ActiveFedora::File"
43
+ end
39
44
 
40
- def lookup_reflection(association_name)
41
- model._reflect_on_association(association_name)
42
- end
45
+ private_class_method :inherit_options_from_association
43
46
  end
44
47
  end
@@ -9,7 +9,7 @@ module ActiveFedora::Associations::Builder
9
9
  super + [:inverse_of, :solr_page_size]
10
10
  end
11
11
 
12
- def validate_options
12
+ def self.validate_options(options)
13
13
  super
14
14
  Deprecation.warn HasAndBelongsToMany, ":solr_page_size doesn't do anything anymore and will be removed in ActiveFedora 10" if options.key?(:solr_page_size)
15
15
  raise "You must specify a predicate for #{name}" unless options[:predicate]
@@ -8,10 +8,8 @@ module ActiveFedora::Associations::Builder
8
8
  super + [:as, :dependent, :inverse_of]
9
9
  end
10
10
 
11
- def build
12
- reflection = super
13
- configure_dependency
14
- reflection
11
+ def self.valid_dependent_options
12
+ [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
15
13
  end
16
14
 
17
15
  def self.define_readers(mixin, name)
@@ -29,44 +27,5 @@ module ActiveFedora::Associations::Builder
29
27
  association(name).ids_writer(ids)
30
28
  end
31
29
  end
32
-
33
- private
34
-
35
- def configure_dependency
36
- return unless options[:dependent]
37
- unless [:destroy, :delete_all, :nullify, :restrict].include?(options[:dependent])
38
- raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, " \
39
- ":nullify or :restrict (#{options[:dependent].inspect})"
40
- end
41
-
42
- send("define_#{options[:dependent]}_dependency_method")
43
- model.before_destroy dependency_method_name
44
- end
45
-
46
- def define_destroy_dependency_method
47
- name = self.name
48
- model.send(:define_method, dependency_method_name) do
49
- send(name).delete_all
50
- end
51
- end
52
-
53
- def define_delete_all_dependency_method
54
- name = self.name
55
- model.send(:define_method, dependency_method_name) do
56
- send(name).delete_all
57
- end
58
- end
59
- alias define_nullify_dependency_method define_delete_all_dependency_method
60
-
61
- def define_restrict_dependency_method
62
- name = self.name
63
- model.send(:define_method, dependency_method_name) do
64
- raise ActiveRecord::DeleteRestrictionError, name unless send(name).empty?
65
- end
66
- end
67
-
68
- def dependency_method_name
69
- "has_many_dependent_for_#{name}"
70
- end
71
30
  end
72
31
  end
@@ -8,12 +8,6 @@ module ActiveFedora::Associations::Builder
8
8
  super + [:has_member_relation, :is_member_of_relation, :inserted_content_relation, :foreign_key, :through] - [:predicate]
9
9
  end
10
10
 
11
- def build
12
- reflection = super
13
- configure_dependency
14
- reflection
15
- end
16
-
17
11
  def self.define_readers(mixin, name)
18
12
  super
19
13
 
@@ -22,7 +16,7 @@ module ActiveFedora::Associations::Builder
22
16
  end
23
17
  end
24
18
 
25
- def validate_options
19
+ def self.validate_options(options)
26
20
  super
27
21
  if !options[:has_member_relation] && !options[:is_member_of_relation]
28
22
  raise ArgumentError, "You must specify a predicate for #{name}"
@@ -8,19 +8,8 @@ module ActiveFedora::Associations::Builder
8
8
  super
9
9
  end
10
10
 
11
- def initialize(model, name, options)
12
- super
13
- @name = :"#{name.to_s.singularize}_ids"
14
- end
15
-
16
- def build
17
- super.tap do |reflection|
18
- model.index_config[name] = build_index_config(reflection)
19
- end
20
- end
21
-
22
- def build_index_config(reflection)
23
- ActiveFedora::Indexing::Map::IndexObject.new(reflection.predicate_for_solr) { |index| index.as :symbol }
11
+ def self.better_name(name)
12
+ :"#{name.to_s.singularize}_ids"
24
13
  end
25
14
  end
26
15
  end
@@ -1,16 +1,12 @@
1
1
  module ActiveFedora::Associations::Builder
2
2
  class SingularAssociation < Association #:nodoc:
3
3
  def self.valid_options(options)
4
- super + [:dependent, :inverse_of]
5
- end
6
-
7
- def self.constructable?
8
- true
4
+ super + [:dependent, :inverse_of, :required]
9
5
  end
10
6
 
11
7
  def self.define_accessors(model, reflection)
12
8
  super
13
- define_constructors(model.generated_association_methods, reflection.name) if constructable?
9
+ define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable?
14
10
  end
15
11
 
16
12
  def self.define_constructors(mixin, name)
@@ -4,9 +4,8 @@ module ActiveFedora::Associations::Builder
4
4
  :singular_rdf
5
5
  end
6
6
 
7
- def initialize(model, name, options)
8
- super
9
- @name = :"#{name}_id"
7
+ def self.better_name(name)
8
+ :"#{name}_id"
10
9
  end
11
10
  end
12
11
  end
@@ -100,7 +100,7 @@ module ActiveFedora
100
100
  # Replace this collection with +other_array+
101
101
  # This will perform a diff and delete/add only records that have changed.
102
102
  def replace(other_array)
103
- other_array.each { |val| raise_on_type_mismatch(val) }
103
+ other_array.each { |val| raise_on_type_mismatch!(val) }
104
104
 
105
105
  load_target
106
106
  other = other_array.size < 100 ? other_array : other_array.to_set
@@ -156,8 +156,7 @@ module ActiveFedora
156
156
  result = true
157
157
 
158
158
  records.flatten.each do |record|
159
- raise_on_type_mismatch(record)
160
- run_type_validator(record)
159
+ raise_on_type_mismatch!(record)
161
160
  add_to_target(record) do |_r|
162
161
  result &&= insert_record(record) unless owner.new_record?
163
162
  end
@@ -356,7 +355,7 @@ module ActiveFedora
356
355
 
357
356
  def delete_or_destroy(records, method)
358
357
  records = records.flatten.select { |x| load_target.include?(x) }
359
- records.each { |record| raise_on_type_mismatch(record) }
358
+ records.each { |record| raise_on_type_mismatch!(record) }
360
359
  existing_records = records.select(&:persisted?)
361
360
 
362
361
  records.each { |record| callback(:before_remove, record) }
@@ -374,20 +373,13 @@ module ActiveFedora
374
373
 
375
374
  def callback(method, record)
376
375
  callbacks_for(method).each do |callback|
377
- case callback
378
- when Symbol
379
- @owner.send(callback, record)
380
- when Proc
381
- callback.call(@owner, record)
382
- else
383
- callback.send(method, @owner, record)
384
- end
376
+ callback.call(method, owner, record)
385
377
  end
386
378
  end
387
379
 
388
380
  def callbacks_for(callback_name)
389
- full_callback_name = "#{callback_name}_for_#{@reflection.name}"
390
- @owner.class.send(full_callback_name.to_sym) || []
381
+ full_callback_name = "#{callback_name}_for_#{reflection.name}"
382
+ owner.class.send(full_callback_name)
391
383
  end
392
384
 
393
385
  def ensure_owner_is_not_new
@@ -33,8 +33,7 @@ module ActiveFedora
33
33
  # Ensures that this association's +type+ is set on the record and adds the record to the association's DirectContainer
34
34
  def replace(record, *)
35
35
  if record
36
- raise_on_type_mismatch(record)
37
- run_type_validator(record)
36
+ raise_on_type_mismatch!(record)
38
37
  remove_existing_target
39
38
  add_type_to_record(record, options[:type])
40
39
  add_to_container(record)
@@ -37,7 +37,7 @@ module ActiveFedora
37
37
  result = true
38
38
 
39
39
  records.flatten.each do |record|
40
- raise_on_type_mismatch(record)
40
+ raise_on_type_mismatch!(record)
41
41
  add_to_target(record) do |_r|
42
42
  result &&= insert_record(record)
43
43
  end
@@ -57,6 +57,29 @@ module ActiveFedora
57
57
  end
58
58
  end
59
59
 
60
+ def handle_dependency
61
+ case options[:dependent]
62
+ when :restrict_with_exception
63
+ raise ActiveFedora::DeleteRestrictionError, reflection.name unless empty?
64
+
65
+ when :restrict_with_error
66
+ unless empty?
67
+ record = owner.class.human_attribute_name(reflection.name).downcase
68
+ owner.errors.add(:base, message || :'restrict_dependent_destroy.has_many', record: record)
69
+ throw(:abort)
70
+ end
71
+
72
+ else
73
+ if options[:dependent] == :destroy
74
+ # No point in executing the counter update since we're going to destroy the parent anyway
75
+ load_target.each { |t| t.destroyed_by_association = reflection }
76
+ destroy_all
77
+ else
78
+ delete_all
79
+ end
80
+ end
81
+ end
82
+
60
83
  protected
61
84
 
62
85
  def find_polymorphic_inverse(record)
@@ -131,7 +131,7 @@ module ActiveFedora
131
131
 
132
132
  def create_singleton_association(file_path)
133
133
  undeclared_files << file_path.to_sym
134
- association = Associations::BasicContainsAssociation.new(self, Reflection::AssociationReflection.new(:contains, file_path, { class_name: 'ActiveFedora::File' }, self.class))
134
+ association = Associations::BasicContainsAssociation.new(self, Reflection::AssociationReflection.new(:contains, file_path, nil, { class_name: 'ActiveFedora::File' }, self.class))
135
135
  @association_cache[file_path.to_sym] = association
136
136
 
137
137
  singleton_class.send :define_method, accessor_name(file_path) do
@@ -14,6 +14,7 @@ module ActiveFedora
14
14
  # Accepts a logger conforming to the interface of Log4r which can be
15
15
  # retrieved on both a class and instance level by calling +logger+.
16
16
  mattr_accessor :logger, instance_writer: false
17
+ mattr_accessor :belongs_to_required_by_default, instance_accessor: false
17
18
  end
18
19
 
19
20
  # Constructor. You may supply a custom +:id+, or we call the Fedora Rest API for the
@@ -31,7 +32,7 @@ module ActiveFedora
31
32
  assign_attributes(attributes) if attributes
32
33
 
33
34
  yield self if block_given?
34
- run_callbacks :initialize
35
+ _run_initialize_callbacks
35
36
  end
36
37
 
37
38
  ##