active-fedora 5.3.1 → 5.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|