active-fedora 9.4.3 → 9.5.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.
@@ -145,15 +145,15 @@ module ActiveFedora
145
145
  assign_rdf_subject
146
146
  serialize_attached_files
147
147
  @ldp_source = @ldp_source.create
148
- assign_uri_to_attached_files
149
- save_attached_files
148
+ assign_uri_to_contained_resources
149
+ save_contained_resources
150
150
  refresh
151
151
  end
152
152
 
153
153
  def update_record(options = {})
154
154
  serialize_attached_files
155
155
  execute_sparql_update
156
- save_attached_files
156
+ save_contained_resources
157
157
  refresh
158
158
  end
159
159
 
@@ -195,16 +195,25 @@ module ActiveFedora
195
195
  ActiveFedora.fedora.connection.put(path, "")
196
196
  end
197
197
 
198
- def assign_uri_to_attached_files
199
- attached_files.each do |name, ds|
200
- ds.uri= "#{uri}/#{name}"
198
+ def assign_uri_to_contained_resources
199
+ contained_resources.each do |name, source|
200
+ source.uri= "#{uri}/#{name}"
201
201
  end
202
202
  end
203
203
 
204
- def save_attached_files
205
- attached_files.select { |_, file| file.changed? }.each do |_, file|
206
- file.save # Don't call save! because if the content_changed? returns false, it'll raise an error.
204
+ def save_contained_resources
205
+ contained_resources.changed.each do |_, resource|
206
+ resource.save
207
207
  end
208
208
  end
209
+
210
+ def contained_resources
211
+ @contained_resources ||= attached_files.merge(contained_rdf_sources)
212
+ end
213
+
214
+ def contained_rdf_sources
215
+ @contained_rdf_sources ||=
216
+ AssociationHash.new(self, self.class.contained_rdf_source_reflections)
217
+ end
209
218
  end
210
219
  end
@@ -41,7 +41,11 @@ module ActiveFedora
41
41
  end
42
42
 
43
43
  def child_resource_reflections
44
- reflections.select { |_, reflection| reflection.kind_of?(AssociationReflection) && reflection.macro == :contains }
44
+ reflections.select { |_, reflection| reflection.kind_of?(AssociationReflection) && reflection.macro == :contains && reflection.klass <= ActiveFedora::File}
45
+ end
46
+
47
+ def contained_rdf_source_reflections
48
+ reflections.select { |_, reflection| reflection.kind_of?(AssociationReflection) && reflection.macro == :contains && !(reflection.klass <= ActiveFedora::File)}
45
49
  end
46
50
 
47
51
  # Returns the AssociationReflection object for the +association+ (use the symbol).
@@ -8,12 +8,12 @@ module ActiveFedora
8
8
  opts = {}
9
9
  opts[:rows] = limit_value if limit_value
10
10
  opts[:sort] = order_values if order_values
11
-
11
+
12
12
  calculate :count, where_values, opts
13
13
  end
14
14
 
15
15
  def calculate(calculation, conditions, opts={})
16
- SolrService.query(create_query(conditions), :raw=>true, :rows=>0)['response']['numFound']
16
+ SolrService.query(create_query(conditions), raw: true, rows: 0).fetch('response'.freeze)['numFound'.freeze]
17
17
  end
18
18
  end
19
19
  end
@@ -111,7 +111,7 @@ module ActiveFedora
111
111
  unless opts.include?(:sort)
112
112
  opts[:sort]=@klass.default_sort_params
113
113
  end
114
- SolrService.query(create_query(conditions), opts)
114
+ SolrService.query(create_query(conditions), opts)
115
115
  end
116
116
 
117
117
  # Yields the found ActiveFedora::Base object to the passed block
@@ -199,7 +199,7 @@ module ActiveFedora
199
199
  # The true class may be a subclass of @klass, so always use from_class_uri
200
200
  resource_class = Model.from_class_uri(has_model_value(resource)) || ActiveFedora::Base
201
201
  unless equivalent_class?(resource_class)
202
- raise ActiveFedora::ActiveFedoraError.new("Model mismatch. Expected #{@klass}. Got: #{resource_class}")
202
+ raise ActiveFedora::ActiveFedoraError.new("Model mismatch. Expected #{@klass}. Got: #{resource_class}")
203
203
  end
204
204
  resource_class
205
205
  end
@@ -254,45 +254,42 @@ module ActiveFedora
254
254
  private
255
255
 
256
256
  # Returns a solr query for the supplied conditions
257
- # @param[Hash] conditions solr conditions to match
257
+ # @param[Hash,String,Array] conditions solr conditions to match
258
258
  def create_query(conditions)
259
- case conditions
260
- when Hash
261
- build_query([create_query_from_hash(conditions)])
262
- when String
263
- build_query(["(#{conditions})"])
264
- else
265
- build_query(conditions)
266
- end
259
+ build_query(build_where(conditions))
267
260
  end
268
261
 
262
+ # @param [Array<String>] conditions
269
263
  def build_query(conditions)
270
- clauses = search_model_clause ? [search_model_clause] : []
271
- clauses += conditions.reject{|c| c.blank?}
264
+ clauses = search_model_clause ? [search_model_clause] : []
265
+ clauses += conditions.reject { |c| c.blank? }
272
266
  return "*:*" if clauses.empty?
273
267
  clauses.compact.join(" AND ")
274
268
  end
275
269
 
270
+ # @param [Hash<Symbol,String>] conditions
271
+ # @returns [Array<String>]
276
272
  def create_query_from_hash(conditions)
277
- conditions.map {|key,value| condition_to_clauses(key, value)}.compact.join(" AND ")
273
+ conditions.map { |key, value| condition_to_clauses(key, value) }.compact
278
274
  end
279
275
 
276
+ # @param [Symbol] key
277
+ # @param [String] value
280
278
  def condition_to_clauses(key, value)
281
- unless value.nil?
282
- # if the key is a property name, turn it into a solr field
283
- if @klass.delegated_attributes.key?(key)
284
- # TODO Check to see if `key' is a possible solr field for this class, if it isn't try :searchable instead
285
- key = ActiveFedora::SolrQueryBuilder.solr_name(key, :stored_searchable, type: :string)
286
- end
279
+ SolrQueryBuilder.construct_query([[field_name_for(key), value]])
280
+ end
287
281
 
288
- if value.empty?
289
- "-#{key}:['' TO *]"
290
- elsif value.is_a? Array
291
- value.map { |val| "#{key}:#{solr_escape(val)}" }
292
- else
293
- key = SOLR_DOCUMENT_ID if (key === :id || key === :id)
294
- "#{key}:#{solr_escape(value)}"
295
- end
282
+ # If the key is a property name, turn it into a solr field
283
+ # @param [Symbol] key
284
+ # @return [String]
285
+ def field_name_for(key)
286
+ if @klass.delegated_attributes.key?(key)
287
+ # TODO Check to see if `key' is a possible solr field for this class, if it isn't try :searchable instead
288
+ ActiveFedora::SolrQueryBuilder.solr_name(key, :stored_searchable, type: :string)
289
+ elsif key == :id
290
+ SOLR_DOCUMENT_ID
291
+ else
292
+ key.to_s
296
293
  end
297
294
  end
298
295
 
@@ -306,13 +303,5 @@ module ActiveFedora
306
303
  clauses.size == 1 ? clauses.first : "(#{clauses.join(" OR ")})"
307
304
  end
308
305
  end
309
-
310
-
311
- private
312
- # Adds esaping for spaces which are not handled by RSolr.solr_escape
313
- # See rsolr/rsolr#101
314
- def solr_escape terms
315
- RSolr.solr_escape(terms).gsub(/\s+/,"\\ ")
316
- end
317
306
  end
318
307
  end
@@ -17,7 +17,7 @@ module ActiveFedora
17
17
  def where_values=(values)
18
18
  raise ImmutableRelation if @loaded
19
19
  @values[:where] = values
20
- end
20
+ end
21
21
 
22
22
  def order_values
23
23
  @values[:order] || []
@@ -62,11 +62,13 @@ module ActiveFedora
62
62
  self
63
63
  end
64
64
 
65
+ # @param [Hash,String] values
66
+ # @return [Array<String>] list of solr hashes
65
67
  def build_where(values)
66
68
  return [] if values.blank?
67
69
  case values
68
70
  when Hash
69
- [create_query_from_hash(values)]
71
+ create_query_from_hash(values)
70
72
  when String
71
73
  ["(#{values})"]
72
74
  else
@@ -76,7 +78,7 @@ module ActiveFedora
76
78
 
77
79
  # Order the returned records by the field and direction provided
78
80
  #
79
- # @option [Array<String>] args a list of fields and directions to sort by
81
+ # @option [Array<String>] args a list of fields and directions to sort by
80
82
  #
81
83
  # @example
82
84
  # Person.where(occupation_s: 'Plumber').order('name_t desc', 'color_t asc')
@@ -1,5 +1,7 @@
1
1
  module ActiveFedora
2
2
  module SolrQueryBuilder
3
+ PARSED_SUFFIX = '_tesim'.freeze
4
+
3
5
  # Construct a solr query for a list of ids
4
6
  # This is used to get a solr response based on the list of ids in an object's RELS-EXT relationhsips
5
7
  # If the id_array is empty, defaults to a query of "id:NEVER_USE_THIS_ID", which will return an empty solr response
@@ -29,18 +31,72 @@ module ActiveFedora
29
31
  # # => _query_:"{!raw f=has_model_ssim}info:fedora/afmodel:ComplexCollection" OR _query_:"{!raw f=has_model_ssim}info:fedora/afmodel:ActiveFedora_Base"
30
32
  #
31
33
  # construct_query_for_rel [[Book.reflect_on_association(:library), "foo/bar/baz"]]
32
- def self.construct_query_for_rel(field_pairs, join_with = 'AND')
34
+ def self.construct_query_for_rel(field_pairs, join_with = ' AND ')
33
35
  field_pairs = field_pairs.to_a if field_pairs.kind_of? Hash
36
+ construct_query(property_values_to_solr(field_pairs), join_with)
37
+ end
34
38
 
35
- clauses = pairs_to_clauses(field_pairs.reject { |_, target_uri| target_uri.blank? })
36
- clauses.empty? ? "id:NEVER_USE_THIS_ID" : clauses.join(" #{join_with} ".freeze)
39
+ # Construct a solr query from a list of pairs (e.g. [field name, values])
40
+ # @param [Array<Array>] pairs a list of pairs of property name and values
41
+ # @param [String] join_with ('AND') the value we're joining the clauses with
42
+ # @returns [String] a solr query
43
+ # @example
44
+ # construct_query([['library_id_ssim', '123'], ['owner_ssim', 'Fred']])
45
+ # # => "_query_:\"{!raw f=library_id_ssim}123\" AND _query_:\"{!raw f=owner_ssim}Fred\""
46
+ def self.construct_query(field_pairs, join_with = ' AND ')
47
+ pairs_to_clauses(field_pairs).join(join_with)
37
48
  end
38
49
 
39
50
  private
40
- # Given an list of 2 element lists, transform to a list of solr clauses
51
+ # @param [Array<Array>] pairs a list of (key, value) pairs. The value itself may
52
+ # @return [Array] a list of solr clauses
41
53
  def self.pairs_to_clauses(pairs)
42
- pairs.map do |field, target_uri|
43
- raw_query(solr_field(field), target_uri)
54
+ pairs.flat_map do |field, value|
55
+ condition_to_clauses(field, value)
56
+ end
57
+ end
58
+
59
+ # @param [String] field
60
+ # @param [String, Array<String>] values
61
+ # @return [Array<String>]
62
+ def self.condition_to_clauses(field, values)
63
+ values = Array(values)
64
+ values << nil if values.empty?
65
+ values.map do |value|
66
+ if value.present?
67
+ if parsed?(field)
68
+ # If you do a raw query on a parsed field you won't get the matches you expect.
69
+ "#{field}:#{solr_escape(value)}"
70
+ else
71
+ raw_query(field, value)
72
+ end
73
+ else
74
+ # Check that the field is not present. In SQL: "WHERE field IS NULL"
75
+ "-#{field}:[* TO *]"
76
+ end
77
+ end
78
+ end
79
+
80
+ def self.parsed?(field)
81
+ field.end_with?(PARSED_SUFFIX)
82
+ end
83
+
84
+ # Adds esaping for spaces which are not handled by RSolr.solr_escape
85
+ # See rsolr/rsolr#101
86
+ def self.solr_escape terms
87
+ RSolr.solr_escape(terms).gsub(/\s+/,"\\ ")
88
+ end
89
+
90
+ # Given a list of pairs (e.g. [field name, values]), convert the field names
91
+ # to solr names
92
+ # @param [Array<Array>] pairs a list of pairs of property name and values
93
+ # @returns [Hash] map of solr fields to values
94
+ # @example
95
+ # property_values_to_solr([['library_id', '123'], ['owner', 'Fred']])
96
+ # # => [['library_id_ssim', '123'], ['owner_ssim', 'Fred']]
97
+ def self.property_values_to_solr(pairs)
98
+ pairs.each_with_object([]) do |(property, value), list|
99
+ list << [solr_field(property), value]
44
100
  end
45
101
  end
46
102
 
@@ -103,8 +103,8 @@ module ActiveFedora
103
103
 
104
104
  def query(query, args={})
105
105
  raw = args.delete(:raw)
106
- args = args.merge(:q=>query, :qt=>'standard')
107
- result = SolrService.instance.conn.get('select', :params=>args)
106
+ args = args.merge(q: query, qt: 'standard')
107
+ result = SolrService.instance.conn.get('select', params: args)
108
108
  return result if raw
109
109
  result['response']['docs']
110
110
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveFedora
2
- VERSION = "9.4.3"
2
+ VERSION = "9.5.0"
3
3
  end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveFedora::Base do
4
+ before do
5
+ class Source < ActiveFedora::Base
6
+ contains :sub_resource, class_name: "Source"
7
+ property :title, predicate: ::RDF::DC.title, multiple: false
8
+ end
9
+ end
10
+ after do
11
+ Object.send(:remove_const, :Source)
12
+ end
13
+
14
+ describe "contains relationships" do
15
+ it "should be able to have RDF sources" do
16
+ s = Source.new
17
+ s.sub_resource.title = "Test"
18
+ expect(s.sub_resource).not_to be_persisted
19
+ expect{ s.save }.not_to raise_error
20
+ s.reload
21
+ expect(s.sub_resource.title).to eq "Test"
22
+ expect(s.sub_resource.uri).to eq s.uri.to_s + "/sub_resource"
23
+ end
24
+ it "should be able to add RDF sources" do
25
+ s = Source.create
26
+ s.sub_resource.title = "Test"
27
+ expect(s.sub_resource).not_to be_persisted
28
+ expect{ s.save }.not_to raise_error
29
+ s.reload
30
+ expect(s.sub_resource.title).to eq "Test"
31
+ expect(s.sub_resource.uri).to eq s.uri.to_s + "/sub_resource"
32
+ end
33
+ end
34
+ end
@@ -30,7 +30,6 @@ describe "scoped queries" do
30
30
  Object.send(:remove_const, :ModelIntegrationSpec)
31
31
  end
32
32
 
33
-
34
33
  describe "When there is one object in the store" do
35
34
  let!(:test_instance) { ModelIntegrationSpec::Basic.create!()}
36
35
 
@@ -67,7 +66,7 @@ describe "scoped queries" do
67
66
  test_instance3.delete
68
67
  end
69
68
 
70
- it "should query" do
69
+ it "queries" do
71
70
  field = ActiveFedora::SolrQueryBuilder.solr_name('foo', type: :string)
72
71
  expect(ModelIntegrationSpec::Basic.where(field => 'Beta')).to eq [test_instance1]
73
72
  expect(ModelIntegrationSpec::Basic.where('foo' => 'Beta')).to eq [test_instance1]
@@ -90,8 +89,13 @@ describe "scoped queries" do
90
89
  expect(ModelIntegrationSpec::Basic.where("foo:bar OR bar:baz").where_values).to eq ["(foo:bar OR bar:baz)"]
91
90
  end
92
91
 
93
- it "should chain where queries" do
94
- expect(ModelIntegrationSpec::Basic.where(ActiveFedora::SolrQueryBuilder.solr_name('bar', type: :string) => 'Peanuts').where("#{ActiveFedora::SolrQueryBuilder.solr_name('foo', type: :string)}:bar").where_values).to eq ["#{ActiveFedora::SolrQueryBuilder.solr_name('bar', type: :string)}:Peanuts", "(#{ActiveFedora::SolrQueryBuilder.solr_name('foo', type: :string)}:bar)"]
92
+ it "chains where queries" do
93
+ first_condition = { ActiveFedora::SolrQueryBuilder.solr_name('bar', type: :string) => 'Peanuts' }
94
+ second_condition = "foo_tesim:bar"
95
+ where_values = ModelIntegrationSpec::Basic.where(first_condition)
96
+ .where(second_condition).where_values
97
+ expect(where_values).to eq ["bar_tesim:Peanuts",
98
+ "(foo_tesim:bar)"]
95
99
  end
96
100
 
97
101
  it "should chain count" do
@@ -4,19 +4,24 @@ describe ActiveFedora::AttachedFiles do
4
4
  subject { ActiveFedora::Base.new }
5
5
  describe "contains" do
6
6
  before do
7
+ class Z < ActiveFedora::File
8
+ end
7
9
  class FooHistory < ActiveFedora::Base
8
10
  contains 'dsid', class_name: 'ActiveFedora::SimpleDatastream'
9
11
  contains 'complex_ds', autocreate: true, class_name: 'Z'
10
12
  contains 'thumbnail'
13
+ contains 'child_resource', class_name: 'ActiveFedora::Base'
11
14
  end
12
15
  end
13
16
  after do
17
+ Object.send(:remove_const, :Z)
14
18
  Object.send(:remove_const, :FooHistory)
15
19
  end
16
20
 
17
21
  it "should have a child_resource_reflection" do
18
22
  expect(FooHistory.child_resource_reflections).to have_key(:dsid)
19
23
  expect(FooHistory.child_resource_reflections).to have_key(:thumbnail)
24
+ expect(FooHistory.child_resource_reflections).not_to have_key(:child_resource)
20
25
  end
21
26
 
22
27
  it "should let you override defaults" do
@@ -34,6 +39,8 @@ describe ActiveFedora::AttachedFiles do
34
39
  before do
35
40
  @original_behavior = Deprecation.default_deprecation_behavior
36
41
  Deprecation.default_deprecation_behavior = :silence
42
+ class Z < ActiveFedora::File
43
+ end
37
44
  class FooHistory < ActiveFedora::Base
38
45
  has_metadata :name => 'dsid', type: ActiveFedora::SimpleDatastream
39
46
  has_metadata 'complex_ds', autocreate: true, type: 'Z'
@@ -42,6 +49,7 @@ describe ActiveFedora::AttachedFiles do
42
49
  after do
43
50
  Deprecation.default_deprecation_behavior = @original_behavior
44
51
  Object.send(:remove_const, :FooHistory)
52
+ Object.send(:remove_const, :Z)
45
53
  end
46
54
 
47
55
  it "should have a child_resource_reflection" do
@@ -39,6 +39,20 @@ describe ActiveFedora::File do
39
39
  it "sets the uri using the parent as the base" do
40
40
  expect(subject).to eq "#{ActiveFedora.fedora.host}#{ActiveFedora.fedora.base_path}/1234/FOO1"
41
41
  end
42
+
43
+ context "and it's initialized with the URI" do
44
+ let(:file) { described_class.new(parent.uri+"/FOO1") }
45
+ it "works" do
46
+ expect(subject.to_s).to eq "#{ActiveFedora.fedora.host}#{ActiveFedora.fedora.base_path}/1234/FOO1"
47
+ end
48
+ end
49
+
50
+ context "and it's initialized with an ID" do
51
+ let(:file) { described_class.new(parent.id+"/FOO1") }
52
+ it "works" do
53
+ expect(subject.to_s).to eq "#{ActiveFedora.fedora.host}#{ActiveFedora.fedora.base_path}/1234/FOO1"
54
+ end
55
+ end
42
56
  end
43
57
 
44
58
  context "when the file doesn't have a uri" do