active-fedora 9.4.3 → 9.5.0

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