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
@@ -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
  ##