active-fedora 5.3.1 → 5.4.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.
- data/History.txt +6 -0
- data/LICENSE +14 -20
- data/lib/active_fedora.rb +2 -0
- data/lib/active_fedora/base.rb +8 -4
- data/lib/active_fedora/datastream.rb +1 -1
- data/lib/active_fedora/datastreams.rb +23 -5
- data/lib/active_fedora/delegating.rb +12 -0
- data/lib/active_fedora/model.rb +0 -177
- data/lib/active_fedora/querying.rb +146 -0
- data/lib/active_fedora/rdf_datastream.rb +1 -1
- data/lib/active_fedora/relation.rb +229 -0
- data/lib/active_fedora/unsaved_digital_object.rb +3 -1
- data/lib/active_fedora/version.rb +1 -1
- data/spec/integration/delete_all_spec.rb +47 -0
- data/spec/integration/ntriples_datastream_spec.rb +38 -0
- data/spec/integration/scoped_query_spec.rb +88 -0
- data/spec/integration/solr_service_spec.rb +3 -2
- data/spec/unit/datastream_spec.rb +1 -1
- data/spec/unit/datastreams_spec.rb +3 -1
- data/spec/unit/model_spec.rb +1 -202
- data/spec/unit/query_spec.rb +208 -0
- data/spec/unit/relationships_spec.rb +1 -0
- data/spec/unit/solr_config_options_spec.rb +1 -2
- metadata +11 -10
- data/.document +0 -5
- data/COPYING.txt +0 -674
- data/COYING.LESSER.txt +0 -165
- data/License.txt +0 -58
- data/Manifest.txt +0 -19
- data/PostInstall.txt +0 -3
- data/TODO +0 -2
@@ -111,7 +111,7 @@ module ActiveFedora
|
|
111
111
|
class TermProxy
|
112
112
|
|
113
113
|
attr_reader :graph, :subject, :predicate
|
114
|
-
delegate :class, :to_s, :==, :kind_of?, :each, :map, :empty?, :as_json, :to => :values
|
114
|
+
delegate :class, :to_s, :==, :kind_of?, :each, :map, :empty?, :as_json, :is_a?, :to => :values
|
115
115
|
|
116
116
|
def initialize(graph, subject, predicate)
|
117
117
|
@graph = graph
|
@@ -0,0 +1,229 @@
|
|
1
|
+
module ActiveFedora
|
2
|
+
class Relation
|
3
|
+
|
4
|
+
attr_reader :loaded
|
5
|
+
alias :loaded? :loaded
|
6
|
+
|
7
|
+
attr_accessor :limit_value, :where_values, :order_values
|
8
|
+
|
9
|
+
def initialize(klass)
|
10
|
+
@klass = klass
|
11
|
+
@loaded = false
|
12
|
+
self.where_values = []
|
13
|
+
self.order_values = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset
|
17
|
+
@first = @loaded = nil
|
18
|
+
@records = []
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Returns the first records that was found.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# Person.where(name_t: 'Jones').first
|
27
|
+
# => #<Person @id="foo:123" @name='Jones' ... >
|
28
|
+
def first
|
29
|
+
if loaded?
|
30
|
+
@records.first
|
31
|
+
else
|
32
|
+
@first ||= limit(1).to_a[0]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Limits the number of returned records to the value specified
|
37
|
+
#
|
38
|
+
# @option [Integer] value the number of records to return
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# Person.where(name_t: 'Jones').limit(10)
|
42
|
+
# => [#<Person @id="foo:123" @name='Jones'>, #<Person @id="foo:125" @name='Jones'>, ...]
|
43
|
+
def limit(value)
|
44
|
+
relation = clone
|
45
|
+
relation.limit_value = value
|
46
|
+
relation
|
47
|
+
end
|
48
|
+
|
49
|
+
# Limits the returned records to those that match the provided search conditions
|
50
|
+
#
|
51
|
+
# @option [Hash] opts a hash of solr conditions
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# Person.where(name_t: 'Mario', occupation_s: 'Plumber')
|
55
|
+
# => [#<Person @id="foo:123" @name='Mario'>, #<Person @id="foo:125" @name='Mario'>, ...]
|
56
|
+
def where(opts)
|
57
|
+
return self if opts.blank?
|
58
|
+
relation = clone
|
59
|
+
relation.where_values = opts
|
60
|
+
relation
|
61
|
+
end
|
62
|
+
|
63
|
+
# Order the returned records by the field and direction provided
|
64
|
+
#
|
65
|
+
# @option [Array<String>] args a list of fields and directions to sort by
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# Person.where(occupation_s: 'Plumber').order('name_t desc', 'color_t asc')
|
69
|
+
# => [#<Person @id="foo:123" @name='Luigi'>, #<Person @id="foo:125" @name='Mario'>, ...]
|
70
|
+
def order(*args)
|
71
|
+
return self if args.blank?
|
72
|
+
|
73
|
+
relation = clone
|
74
|
+
relation.order_values += args.flatten
|
75
|
+
relation
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns an Array of objects of the Class that +find+ is being
|
79
|
+
# called on
|
80
|
+
#
|
81
|
+
# @param[String,Symbol,Hash] args either a pid or :all or a hash of conditions
|
82
|
+
# @param [Hash] opts the options to create a message with.
|
83
|
+
# @option opts [Integer] :rows when :all is passed, the maximum number of rows to load from solr
|
84
|
+
# @option opts [Boolean] :cast when true, examine the model and cast it to the first known cModel
|
85
|
+
def find(*args)
|
86
|
+
return to_a.find { |*block_args| yield(*block_args) } if block_given?
|
87
|
+
options = args.extract_options!
|
88
|
+
|
89
|
+
# TODO is there any reason not to cast?
|
90
|
+
cast = options.delete(:cast)
|
91
|
+
if options[:sort]
|
92
|
+
# Deprecate sort sometime?
|
93
|
+
sort = options.delete(:sort)
|
94
|
+
options[:order] ||= sort if sort.present?
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
if options.present?
|
99
|
+
options = {conditions: options}
|
100
|
+
apply_finder_options(options).all
|
101
|
+
else
|
102
|
+
case args.first
|
103
|
+
when :first, :last, :all
|
104
|
+
send(args.first)
|
105
|
+
else
|
106
|
+
find_with_ids(args, cast)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def find_with_ids(ids, cast)
|
112
|
+
expects_array = ids.first.kind_of?(Array)
|
113
|
+
return ids.first if expects_array && ids.first.empty?
|
114
|
+
|
115
|
+
ids = ids.flatten.compact.uniq
|
116
|
+
|
117
|
+
case ids.size
|
118
|
+
when 0
|
119
|
+
raise ArgumentError, "Couldn't find #{@klass.name} without an ID"
|
120
|
+
when 1
|
121
|
+
result = @klass.find_one(ids.first, cast)
|
122
|
+
expects_array ? [ result ] : result
|
123
|
+
else
|
124
|
+
find_some(ids, cast)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def find_some(ids, cast)
|
129
|
+
ids.map{|id| @klass.find_one(id, cast)}
|
130
|
+
end
|
131
|
+
|
132
|
+
# A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
|
133
|
+
# same arguments to this method as you can to <tt>find(:all)</tt>.
|
134
|
+
def all(*args)
|
135
|
+
args.any? ? apply_finder_options(args.first).to_a : to_a
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
|
140
|
+
def to_a
|
141
|
+
return @records if loaded?
|
142
|
+
args = {} #:cast=>true}
|
143
|
+
args[:rows] = @limit_value if @limit_value
|
144
|
+
args[:sort] = @order_values if @order_values
|
145
|
+
|
146
|
+
query = @where_values.present? ? @where_values : {}
|
147
|
+
@records = @klass.to_enum(:find_each, query, args).to_a
|
148
|
+
|
149
|
+
@records
|
150
|
+
end
|
151
|
+
|
152
|
+
def ==(other)
|
153
|
+
case other
|
154
|
+
when Relation
|
155
|
+
other.where_values == where_values
|
156
|
+
when Array
|
157
|
+
to_a == other
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def inspect
|
162
|
+
to_a.inspect
|
163
|
+
end
|
164
|
+
|
165
|
+
# Destroys the records matching +conditions+ by instantiating each
|
166
|
+
# record and calling its +destroy+ method. Each object's callbacks are
|
167
|
+
# executed (including <tt>:dependent</tt> association options and
|
168
|
+
# +before_destroy+/+after_destroy+ Observer methods). Returns the
|
169
|
+
# collection of objects that were destroyed; each will be frozen, to
|
170
|
+
# reflect that no changes should be made (since they can't be
|
171
|
+
# persisted).
|
172
|
+
#
|
173
|
+
# Note: Instantiation, callback execution, and deletion of each
|
174
|
+
# record can be time consuming when you're removing many records at
|
175
|
+
# once. It generates at least one fedora +DELETE+ query per record (or
|
176
|
+
# possibly more, to enforce your callbacks). If you want to delete many
|
177
|
+
# rows quickly, without concern for their associations or callbacks, use
|
178
|
+
# +delete_all+ instead.
|
179
|
+
#
|
180
|
+
# ==== Parameters
|
181
|
+
#
|
182
|
+
# * +conditions+ - A string, array, or hash that specifies which records
|
183
|
+
# to destroy. If omitted, all records are destroyed. See the
|
184
|
+
# Conditions section in the ActiveFedora::Relation#where for
|
185
|
+
# more information.
|
186
|
+
#
|
187
|
+
# ==== Examples
|
188
|
+
#
|
189
|
+
# Person.destroy_all(:status_s => "inactive")
|
190
|
+
# Person.where(:age_i => 18).destroy_all
|
191
|
+
def destroy_all(conditions = nil)
|
192
|
+
if conditions
|
193
|
+
where(conditions).destroy_all
|
194
|
+
else
|
195
|
+
to_a.each {|object| object.destroy }.tap { reset }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def delete_all(conditions = nil)
|
200
|
+
if conditions
|
201
|
+
where(conditions).delete_all
|
202
|
+
else
|
203
|
+
to_a.each {|object| object.delete }.tap { reset }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
VALID_FIND_OPTIONS = [:order, :limit, :conditions, :cast]
|
211
|
+
|
212
|
+
def apply_finder_options(options)
|
213
|
+
relation = clone
|
214
|
+
return relation unless options
|
215
|
+
|
216
|
+
options.assert_valid_keys(VALID_FIND_OPTIONS)
|
217
|
+
finders = options.dup
|
218
|
+
finders.delete_if { |key, value| value.nil? && key != :limit }
|
219
|
+
|
220
|
+
([:order,:limit] & finders.keys).each do |finder|
|
221
|
+
relation = relation.send(finder, finders[finder])
|
222
|
+
end
|
223
|
+
|
224
|
+
relation = relation.where(finders[:conditions]) if options.has_key?(:conditions)
|
225
|
+
relation
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
end
|
@@ -3,6 +3,8 @@ module ActiveFedora
|
|
3
3
|
class UnsavedDigitalObject
|
4
4
|
include DigitalObject::DatastreamBootstrap
|
5
5
|
attr_accessor :original_class, :ownerId, :datastreams, :label, :namespace
|
6
|
+
|
7
|
+
PLACEHOLDER = '__DO_NOT_USE__'
|
6
8
|
|
7
9
|
def initialize(original_class, namespace, pid=nil)
|
8
10
|
@pid = pid
|
@@ -12,7 +14,7 @@ module ActiveFedora
|
|
12
14
|
end
|
13
15
|
|
14
16
|
def pid
|
15
|
-
@pid ||
|
17
|
+
@pid || PLACEHOLDER
|
16
18
|
end
|
17
19
|
|
18
20
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveFedora::Base do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
module SpecModel
|
7
|
+
class Basic < ActiveFedora::Base
|
8
|
+
class_attribute :callback_counter
|
9
|
+
|
10
|
+
before_destroy :inc_counter
|
11
|
+
|
12
|
+
def inc_counter
|
13
|
+
self.class.callback_counter += 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
after(:all) do
|
20
|
+
Object.send(:remove_const, :SpecModel)
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
SpecModel::Basic.create!
|
25
|
+
SpecModel::Basic.create!
|
26
|
+
SpecModel::Basic.callback_counter = 0
|
27
|
+
@count = SpecModel::Basic.count
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
describe ".destroy_all" do
|
32
|
+
it "should remove both and run callbacks" do
|
33
|
+
SpecModel::Basic.destroy_all
|
34
|
+
SpecModel::Basic.count.should == @count - 2
|
35
|
+
SpecModel::Basic.callback_counter.should == 2
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe ".delete_all" do
|
41
|
+
it "should remove both and not run callbacks" do
|
42
|
+
SpecModel::Basic.delete_all
|
43
|
+
SpecModel::Basic.count.should == @count - 2
|
44
|
+
SpecModel::Basic.callback_counter.should == 0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -82,6 +82,41 @@ describe ActiveFedora::NtriplesRDFDatastream do
|
|
82
82
|
@subject.save
|
83
83
|
end
|
84
84
|
|
85
|
+
it "should load n-triples into the graph" do
|
86
|
+
ntrip = '<http://oregondigital.org/ns/62> <http://purl.org/dc/terms/type> "Image" .
|
87
|
+
<http://oregondigital.org/ns/62> <http://purl.org/dc/terms/spatial> "Benton County (Ore.)" .
|
88
|
+
'
|
89
|
+
@subject.rdf.content = ntrip
|
90
|
+
@subject.rdf.graph.dump(:ntriples).should == ntrip
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "using rdf_subject" do
|
94
|
+
before do
|
95
|
+
# reopening existing class
|
96
|
+
class MyDatastream < ActiveFedora::NtriplesRDFDatastream
|
97
|
+
rdf_subject { |ds| RDF::URI.new("http://oregondigital.org/ns/#{ds.pid.split(':')[1]}") }
|
98
|
+
map_predicates do |map|
|
99
|
+
map.type(:in => RDF::DC)
|
100
|
+
map.spatial(:in => RDF::DC)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
after do
|
105
|
+
@subject.destroy
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should write rdf with proper subjects" do
|
109
|
+
@subject.rdf.type = "Frog"
|
110
|
+
@subject.inner_object.pid = 'foo:99'
|
111
|
+
@subject.save!
|
112
|
+
@subject.reload
|
113
|
+
@subject.rdf.graph.dump(:ntriples).should == "<http://oregondigital.org/ns/99> <http://purl.org/dc/terms/type> \"Frog\" .\n"
|
114
|
+
@subject.rdf.type == ['Frog']
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
85
120
|
|
86
121
|
it "should delete values" do
|
87
122
|
@subject.title = "Hamlet"
|
@@ -148,5 +183,8 @@ describe ActiveFedora::NtriplesRDFDatastream do
|
|
148
183
|
@subject.title.delete("title1", "title2", "title3")
|
149
184
|
@subject.title.empty?.should be_true
|
150
185
|
end
|
186
|
+
it "should support the is_a? method" do
|
187
|
+
@subject.title.is_a?(Array).should == true
|
188
|
+
end
|
151
189
|
end
|
152
190
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveFedora::Model do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
module ModelIntegrationSpec
|
7
|
+
class Basic < ActiveFedora::Base
|
8
|
+
has_metadata :name => "properties", :type => ActiveFedora::SimpleDatastream do |m|
|
9
|
+
m.field "foo", :string
|
10
|
+
m.field "bar", :string
|
11
|
+
m.field "baz", :string
|
12
|
+
end
|
13
|
+
|
14
|
+
delegate_to :properties, [:foo, :bar, :baz]
|
15
|
+
|
16
|
+
def to_solr(doc = {})
|
17
|
+
doc = super
|
18
|
+
doc['foo_sort'] = doc['foo_t']
|
19
|
+
doc
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
after(:each) do
|
28
|
+
Object.send(:remove_const, :ModelIntegrationSpec)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
describe "When there is one object in the store" do
|
33
|
+
before do
|
34
|
+
@test_instance = ModelIntegrationSpec::Basic.new
|
35
|
+
@test_instance.save
|
36
|
+
end
|
37
|
+
|
38
|
+
after do
|
39
|
+
@test_instance.delete
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
describe ".all" do
|
44
|
+
it "should return an array of instances of the calling Class" do
|
45
|
+
result = ModelIntegrationSpec::Basic.all
|
46
|
+
result.should be_instance_of(Array)
|
47
|
+
# this test is meaningless if the array length is zero
|
48
|
+
result.length.should > 0
|
49
|
+
result.each do |obj|
|
50
|
+
obj.class.should == ModelIntegrationSpec::Basic
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe ".first" do
|
56
|
+
it "should return one instance of the calling class" do
|
57
|
+
ModelIntegrationSpec::Basic.first.should == @test_instance
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "with multiple objects" do
|
63
|
+
before do
|
64
|
+
@test_instance1 = ModelIntegrationSpec::Basic.create!(:foo=>'Beta', :bar=>'Chips')
|
65
|
+
@test_instance2 = ModelIntegrationSpec::Basic.create!(:foo=>'Alpha', :bar=>'Peanuts')
|
66
|
+
@test_instance3 = ModelIntegrationSpec::Basic.create!(:foo=>'Sigma', :bar=>'Peanuts')
|
67
|
+
end
|
68
|
+
after do
|
69
|
+
@test_instance1.delete
|
70
|
+
@test_instance2.delete
|
71
|
+
@test_instance3.delete
|
72
|
+
end
|
73
|
+
it "should query" do
|
74
|
+
ModelIntegrationSpec::Basic.where(:foo_t => 'Beta').should == [@test_instance1]
|
75
|
+
end
|
76
|
+
it "should order" do
|
77
|
+
ModelIntegrationSpec::Basic.order('foo_sort asc').should == [@test_instance2, @test_instance1, @test_instance3]
|
78
|
+
end
|
79
|
+
it "should limit" do
|
80
|
+
ModelIntegrationSpec::Basic.limit(1).should == [@test_instance1]
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should chain them" do
|
84
|
+
ModelIntegrationSpec::Basic.where(:bar_t => 'Peanuts').order('foo_sort asc').limit(1).should == [@test_instance2]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|