active-fedora 9.10.4 → 9.11.0

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