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.
@@ -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 || '__DO_NOT_USE__'
17
+ @pid || PLACEHOLDER
16
18
  end
17
19
 
18
20
 
@@ -1,3 +1,3 @@
1
1
  module ActiveFedora
2
- VERSION = "5.3.1"
2
+ VERSION = "5.4.0"
3
3
  end
@@ -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
+