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.
@@ -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
+