active-fedora 9.10.4 → 9.11.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/History.txt +0 -13
  4. data/lib/active_fedora.rb +7 -0
  5. data/lib/active_fedora/aggregation.rb +11 -0
  6. data/lib/active_fedora/aggregation/base_extension.rb +17 -0
  7. data/lib/active_fedora/aggregation/list_source.rb +103 -0
  8. data/lib/active_fedora/aggregation/ordered_reader.rb +27 -0
  9. data/lib/active_fedora/aggregation/proxy.rb +18 -0
  10. data/lib/active_fedora/associations.rb +40 -0
  11. data/lib/active_fedora/associations/builder/aggregation.rb +51 -0
  12. data/lib/active_fedora/associations/builder/filter.rb +18 -0
  13. data/lib/active_fedora/associations/builder/orders.rb +63 -0
  14. data/lib/active_fedora/associations/filter_association.rb +71 -0
  15. data/lib/active_fedora/associations/orders_association.rb +149 -0
  16. data/lib/active_fedora/attached_files.rb +2 -1
  17. data/lib/active_fedora/attribute_methods.rb +4 -4
  18. data/lib/active_fedora/autosave_association.rb +14 -0
  19. data/lib/active_fedora/base.rb +1 -0
  20. data/lib/active_fedora/cleaner.rb +1 -1
  21. data/lib/active_fedora/errors.rb +3 -0
  22. data/lib/active_fedora/fedora.rb +17 -7
  23. data/lib/active_fedora/file/streaming.rb +13 -4
  24. data/lib/active_fedora/orders.rb +12 -0
  25. data/lib/active_fedora/orders/collection_proxy.rb +8 -0
  26. data/lib/active_fedora/orders/list_node.rb +161 -0
  27. data/lib/active_fedora/orders/ordered_list.rb +264 -0
  28. data/lib/active_fedora/orders/target_proxy.rb +60 -0
  29. data/lib/active_fedora/reflection.rb +215 -42
  30. data/lib/active_fedora/validations.rb +1 -1
  31. data/lib/active_fedora/version.rb +1 -1
  32. data/lib/generators/active_fedora/config/solr/templates/solr/config/schema.xml +1 -1
  33. data/solr/config/schema.xml +1 -1
  34. data/spec/integration/attributes_spec.rb +8 -0
  35. data/spec/integration/file_spec.rb +22 -0
  36. data/spec/integration/has_many_associations_spec.rb +23 -0
  37. data/spec/integration/versionable_spec.rb +6 -6
  38. data/spec/unit/active_fedora_spec.rb +1 -1
  39. data/spec/unit/aggregation/list_source_spec.rb +134 -0
  40. data/spec/unit/aggregation/ordered_reader_spec.rb +43 -0
  41. data/spec/unit/fedora_spec.rb +1 -1
  42. data/spec/unit/filter_spec.rb +133 -0
  43. data/spec/unit/ordered_spec.rb +369 -0
  44. data/spec/unit/orders/list_node_spec.rb +151 -0
  45. data/spec/unit/orders/ordered_list_spec.rb +335 -0
  46. data/spec/unit/orders/reflection_spec.rb +22 -0
  47. data/spec/unit/reflection_spec.rb +2 -4
  48. metadata +25 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 45d18ff02bbb3fd1572b965ee3ab6a3264e12a39
4
- data.tar.gz: b0dd1b79c85e2a29c374139b78ca20714a9dd649
3
+ metadata.gz: bce277ca040d2e5d2b6f20ad12ded43a0d09a81e
4
+ data.tar.gz: 41626c552b9571d1993bc95f3719a0b12a24eb5b
5
5
  SHA512:
6
- metadata.gz: bc801e3d47c9284db763430bfd1adc32909189ce9d5d5a92eddf327820ad840e21a51ed072c41e911ff12675ca7aee227d2ec460cbff570a4e1d240eff4514fd
7
- data.tar.gz: 3abc7c3ca73df8560b491d2d2b07eb17e1675f2ff38745adacc748ad45604e1b8e859307927e0bae8e88d5edd6a092056b992d8de84d0407e49fd71c3f466262
6
+ metadata.gz: b48ed0579be52f0683f6293adc4ff4908706d125f924119cabc2e01076bfcf2501e7732ef9b057948f6aeffd0f10679626d033aab52313523839020005d543ac
7
+ data.tar.gz: 2dfa7a8abb57bc8dc376da0767fd5d70ecf13bc9bbcb6f0542faaad5b8d70973367c1f81776aa8e4e704655029676b9ec124836f3736d1940c36a7e470b3092d
@@ -87,6 +87,7 @@ Metrics/ClassLength:
87
87
  - 'lib/active_fedora/associations/collection_proxy.rb'
88
88
  - 'lib/active_fedora/associations/collection_association.rb'
89
89
  - 'lib/active_fedora/reflection.rb'
90
+ - 'lib/active_fedora/orders/ordered_list.rb'
90
91
 
91
92
  Metrics/MethodLength:
92
93
  Enabled: false
@@ -123,6 +124,8 @@ Style/PredicateName:
123
124
  - 'lib/active_fedora/attached_files.rb'
124
125
  - 'lib/active_fedora/associations.rb'
125
126
  - 'lib/active_fedora/association_hash.rb'
127
+ - 'lib/active_fedora/aggregation/list_source.rb'
128
+ - 'lib/active_fedora/associations/builder/aggregation.rb'
126
129
 
127
130
  Style/GuardClause:
128
131
  Exclude:
@@ -1,16 +1,3 @@
1
- v9.10.4
2
- 2016-03-28: Use a version of activesupport >= 4.2.4 [Justin Coyne]
3
-
4
- v9.10.3
5
- 2016-03-28: Revert "ActiveFedora::File.mime_type should be updatable" [Justin
6
- Coyne]
7
-
8
- 2016-03-28: Pin to rubocop 0.38 [Justin Coyne]
9
-
10
- v9.10.2
11
- 2016-03-26: Pass the block from find_in_batches to search_in_batches [Justin
12
- Coyne]
13
-
14
1
  v9.10.1
15
2
  2016-03-26: Fix #reflect_on_association. [Trey Pendragon]
16
3
 
@@ -36,6 +36,7 @@ module ActiveFedora #:nodoc:
36
36
  extend ActiveSupport::Autoload
37
37
 
38
38
  eager_autoload do
39
+ autoload :Aggregation
39
40
  autoload :AssociationHash
40
41
  autoload :AssociationRelation
41
42
  autoload :Associations
@@ -76,6 +77,7 @@ module ActiveFedora #:nodoc:
76
77
  autoload :FilePersistence
77
78
  autoload :FileRelation
78
79
  autoload :FilesHash
80
+ autoload :Filter
79
81
  autoload :FixityService
80
82
  autoload :Identifiable
81
83
  autoload :Indexers
@@ -94,6 +96,7 @@ module ActiveFedora #:nodoc:
94
96
  autoload :NomDatastream
95
97
  autoload :NullRelation
96
98
  autoload :OmDatastream
99
+ autoload :Orders
97
100
  autoload :Pathing
98
101
  autoload :Persistence
99
102
  autoload :ProfileIndexingService
@@ -227,6 +230,10 @@ module ActiveFedora #:nodoc:
227
230
  ActiveFedora::SolrService.instance
228
231
  end
229
232
 
233
+ def reset_fedora!
234
+ @fedora = nil
235
+ end
236
+
230
237
  def fedora
231
238
  @fedora ||= Fedora.new(fedora_config.credentials)
232
239
  end
@@ -0,0 +1,11 @@
1
+ module ActiveFedora
2
+ module Aggregation
3
+ extend ActiveSupport::Autoload
4
+ eager_autoload do
5
+ autoload :Proxy
6
+ autoload :BaseExtension
7
+ autoload :OrderedReader
8
+ autoload :ListSource
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveFedora::Aggregation
2
+ module BaseExtension
3
+ def ordered_by
4
+ ordered_by_ids.lazy.map { |x| ActiveFedora::Base.find(x) }
5
+ end
6
+
7
+ private
8
+
9
+ def ordered_by_ids
10
+ if id.present?
11
+ ActiveFedora::SolrService.query("{!join from=proxy_in_ssi to=id}ordered_targets_ssim:#{id}").map { |x| x["id"] }
12
+ else
13
+ []
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,103 @@
1
+ module ActiveFedora
2
+ module Aggregation
3
+ class ListSource < ActiveFedora::Base
4
+ delegate :order_will_change!, to: :ordered_self
5
+ property :head, predicate: ::RDF::Vocab::IANA['first'], multiple: false
6
+ property :tail, predicate: ::RDF::Vocab::IANA.last, multiple: false
7
+ property :nodes, predicate: ::RDF::Vocab::DC.hasPart
8
+
9
+ def save(*args)
10
+ return true if has_unpersisted_proxy_for? || !changed?
11
+ persist_ordered_self if ordered_self.changed?
12
+ super
13
+ end
14
+
15
+ # Overriding so that we don't track previously_changed, which was
16
+ # rather expensive.
17
+ def clear_changed_attributes
18
+ @changed_attributes.clear
19
+ end
20
+
21
+ def changed?
22
+ super || ordered_self.changed?
23
+ end
24
+
25
+ # Ordered list representation of proxies in graph.
26
+ # @return [Array<ListNode>]
27
+ def ordered_self
28
+ @ordered_self ||= ordered_list_factory.new(resource, head_subject, tail_subject)
29
+ end
30
+
31
+ # Allow this to be set so that -=, += will work.
32
+ # @param [ActiveFedora::Orders::OrderedList] An ordered list object this
33
+ # graph should contain.
34
+ attr_writer :ordered_self
35
+
36
+ # Serializing head/tail/nodes slows things down CONSIDERABLY, and is not
37
+ # useful.
38
+ # @note This method is used by ActiveFedora::Base upstream for indexing,
39
+ # at https://github.com/projecthydra/active_fedora/blob/master/lib/active_fedora/profile_indexing_service.rb.
40
+ def serializable_hash(_options = nil)
41
+ {}
42
+ end
43
+
44
+ def to_solr(solr_doc = {})
45
+ super.merge(ordered_targets_ssim: ordered_self.target_ids,
46
+ proxy_in_ssi: ordered_self.proxy_in.to_s)
47
+ end
48
+
49
+ # Not useful and slows down indexing.
50
+ def create_date
51
+ nil
52
+ end
53
+
54
+ # Not useful, slows down indexing.
55
+ def modified_date
56
+ nil
57
+ end
58
+
59
+ # Not useful, slows down indexing.
60
+ def has_model
61
+ ["ActiveFedora::Aggregation::ListSource"]
62
+ end
63
+
64
+ private
65
+
66
+ def persist_ordered_self
67
+ nodes_will_change!
68
+ # Delete old statements
69
+ subj = resource.subjects.to_a.select { |x| x.to_s.split("/").last.to_s.include?("#g") }
70
+ subj.each do |s|
71
+ resource.delete [s, nil, nil]
72
+ end
73
+ # Assert head and tail
74
+ self.head = ordered_self.head.next.rdf_subject
75
+ self.tail = ordered_self.tail.prev.rdf_subject
76
+ graph = ordered_self.to_graph
77
+ resource << graph
78
+ # Set node subjects to a term in AF JUST so that AF will persist the
79
+ # sub-graphs.
80
+ # TODO: Find a way to fix this.
81
+ self.nodes = nil
82
+ self.nodes += graph.subjects.to_a
83
+ ordered_self.changes_committed!
84
+ end
85
+
86
+ def has_unpersisted_proxy_for?
87
+ ordered_self.select(&:new_record?).map(&:target).find { |x| x.respond_to?(:uri) }
88
+ end
89
+
90
+ def head_subject
91
+ head_id.first
92
+ end
93
+
94
+ def tail_subject
95
+ tail_id.first
96
+ end
97
+
98
+ def ordered_list_factory
99
+ ActiveFedora::Orders::OrderedList
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,27 @@
1
+ module ActiveFedora::Aggregation
2
+ ##
3
+ # Lazily iterates over a doubly linked list, fixing up nodes if necessary.
4
+ class OrderedReader
5
+ include Enumerable
6
+ attr_reader :root
7
+ def initialize(root)
8
+ @root = root
9
+ end
10
+
11
+ def each
12
+ proxy = first_head
13
+ while proxy
14
+ yield proxy unless proxy.nil?
15
+ next_proxy = proxy.next
16
+ next_proxy.try(:prev=, proxy) if next_proxy && next_proxy.prev != proxy
17
+ proxy = next_proxy
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def first_head
24
+ root.head
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ module ActiveFedora::Aggregation
2
+ class Proxy < ActiveFedora::Base
3
+ belongs_to :container, predicate: ::RDF::Vocab::ORE.proxyIn, class_name: 'ActiveFedora::Base'
4
+ belongs_to :target, predicate: ::RDF::Vocab::ORE.proxyFor, class_name: 'ActiveFedora::Base'
5
+ belongs_to :next, predicate: ::RDF::Vocab::IANA.next, class_name: 'ActiveFedora::Aggregation::Proxy'
6
+ belongs_to :prev, predicate: ::RDF::Vocab::IANA.prev, class_name: 'ActiveFedora::Aggregation::Proxy'
7
+
8
+ type ::RDF::Vocab::ORE.Proxy
9
+
10
+ def as_list
11
+ if self.next
12
+ [self] + self.next.as_list
13
+ else
14
+ [self]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -29,6 +29,8 @@ module ActiveFedora
29
29
  autoload :DirectlyContainsOneAssociation
30
30
  autoload :IndirectlyContainsAssociation
31
31
  autoload :ContainsAssociation
32
+ autoload :FilterAssociation
33
+ autoload :OrdersAssociation
32
34
  autoload :DeleteProxy
33
35
  autoload :ContainedFinder
34
36
  autoload :RecordComposite
@@ -50,6 +52,10 @@ module ActiveFedora
50
52
 
51
53
  autoload :Property, 'active_fedora/associations/builder/property'
52
54
  autoload :SingularProperty, 'active_fedora/associations/builder/singular_property'
55
+
56
+ autoload :Aggregation, 'active_fedora/associations/builder/aggregation'
57
+ autoload :Filter, 'active_fedora/associations/builder/filter'
58
+ autoload :Orders, 'active_fedora/associations/builder/orders'
53
59
  end
54
60
 
55
61
  eager_autoload do
@@ -280,6 +286,40 @@ module ActiveFedora
280
286
  Builder::HasAndBelongsToMany.build(self, name, options)
281
287
  Builder::Property.build(self, name, options.slice(:class_name, :predicate))
282
288
  end
289
+
290
+ ##
291
+ # Allows ordering of an association
292
+ # @example
293
+ # class Image < ActiveFedora::Base
294
+ # contains :list_resource, class_name:
295
+ # "ActiveFedora::Aggregation::ListSource"
296
+ # orders :generic_files, through: :list_resource
297
+ # end
298
+ def orders(name, options = {})
299
+ Builder::Orders.build(self, name, options)
300
+ end
301
+
302
+ ##
303
+ # Convenience method for building an ordered aggregation.
304
+ # @example
305
+ # class Image < ActiveFedora::Base
306
+ # ordered_aggregation :members, through: :list_source
307
+ # end
308
+ def ordered_aggregation(name, options = {})
309
+ Builder::Aggregation.build(self, name, options)
310
+ end
311
+
312
+ ##
313
+ # Create an association filter on the class
314
+ # @example
315
+ # class Image < ActiveFedora::Base
316
+ # aggregates :generic_files
317
+ # filters_association :generic_files, as: :large_files, condition: :big_file?
318
+ # end
319
+ def filters_association(extending_from, options = {})
320
+ name = options.delete(:as)
321
+ Builder::Filter.build(self, name, options.merge(extending_from: extending_from))
322
+ end
283
323
  end
284
324
  end
285
325
  end
@@ -0,0 +1,51 @@
1
+ module ActiveFedora::Associations::Builder
2
+ class Aggregation < ActiveFedora::Associations::Builder::Association
3
+ def self.valid_options(_options)
4
+ [:through, :class_name, :has_member_relation, :type_validator]
5
+ end
6
+
7
+ def self.build(model, name, options)
8
+ model.indirectly_contains name, { has_member_relation: has_member_relation(options), through: proxy_class, foreign_key: proxy_foreign_key, inserted_content_relation: inserted_content_relation }.merge(indirect_options(options))
9
+ model.contains contains_key(options), class_name: list_source_class
10
+ model.orders name, through: contains_key(options)
11
+ end
12
+
13
+ def self.indirect_options(options)
14
+ {
15
+ class_name: options[:class_name],
16
+ type_validator: options[:type_validator]
17
+ }.select { |_k, v| v.present? }
18
+ end
19
+ private_class_method :indirect_options
20
+
21
+ def self.has_member_relation(options)
22
+ options[:has_member_relation] || ::RDF::DC.hasPart
23
+ end
24
+ private_class_method :has_member_relation
25
+
26
+ def self.inserted_content_relation
27
+ ::RDF::Vocab::ORE.proxyFor
28
+ end
29
+ private_class_method :inserted_content_relation
30
+
31
+ def self.proxy_class
32
+ "ActiveFedora::Aggregation::Proxy"
33
+ end
34
+ private_class_method :proxy_class
35
+
36
+ def self.proxy_foreign_key
37
+ :target
38
+ end
39
+ private_class_method :proxy_foreign_key
40
+
41
+ def self.contains_key(options)
42
+ options[:through]
43
+ end
44
+ private_class_method :contains_key
45
+
46
+ def self.list_source_class
47
+ "ActiveFedora::Aggregation::ListSource"
48
+ end
49
+ private_class_method :list_source_class
50
+ end
51
+ end
@@ -0,0 +1,18 @@
1
+ module ActiveFedora::Associations::Builder
2
+ class Filter < ActiveFedora::Associations::Builder::CollectionAssociation
3
+ def self.valid_options(options)
4
+ super + [:extending_from, :condition]
5
+ end
6
+
7
+ def self.macro
8
+ :filter
9
+ end
10
+
11
+ def self.define_readers(mixin, name)
12
+ super
13
+ mixin.redefine_method("#{name.to_s.singularize}_ids") do
14
+ association(name).ids_reader
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,63 @@
1
+ module ActiveFedora::Associations::Builder
2
+ class Orders < ActiveFedora::Associations::Builder::CollectionAssociation
3
+ include ActiveFedora::AutosaveAssociation::AssociationBuilderExtension
4
+ def self.macro
5
+ :orders
6
+ end
7
+
8
+ def self.valid_options(options)
9
+ super + [:through, :unordered_reflection]
10
+ end
11
+
12
+ def self.define_readers(mixin, name)
13
+ super
14
+ mixin.redefine_method(target_accessor(name)) do
15
+ association(name).target_reader
16
+ end
17
+ mixin.redefine_method("#{target_accessor(name)}=") do |nodes|
18
+ association(name).target_writer(nodes)
19
+ end
20
+ end
21
+
22
+ def self.build(model, name, options)
23
+ options = { unordered_reflection: unordered_reflection(model, name) }.merge(options)
24
+ name = :"ordered_#{name.to_s.singularize}_proxies"
25
+ model.property :head, predicate: ::RDF::Vocab::IANA['first']
26
+ model.property :tail, predicate: ::RDF::Vocab::IANA.last
27
+ model.send(:define_method, :apply_first_and_last) do
28
+ source = send(options[:through])
29
+ source.save
30
+ return if head.map(&:rdf_subject) == source.head_id && tail.map(&:rdf_subject) == source.tail_id
31
+ self.head = source.head_id
32
+ self.tail = source.tail_id
33
+ save! if changed?
34
+ end
35
+ model.include ActiveFedora::Associations::Builder::Orders::FixFirstLast
36
+ super
37
+ end
38
+
39
+ module FixFirstLast
40
+ def save(*args)
41
+ super.tap do |result|
42
+ apply_first_and_last if result
43
+ end
44
+ end
45
+
46
+ def save!(*args)
47
+ super.tap do |result|
48
+ apply_first_and_last if result
49
+ end
50
+ end
51
+ end
52
+
53
+ def self.target_accessor(name)
54
+ name.to_s.gsub("_proxies", "").pluralize
55
+ end
56
+ private_class_method :target_accessor
57
+
58
+ def self.unordered_reflection(model, original_name)
59
+ model._reflect_on_association(original_name)
60
+ end
61
+ private_class_method :unordered_reflection
62
+ end
63
+ end