active-fedora 2.3.8 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -1
- data/Gemfile.lock +16 -10
- data/History.txt +4 -5
- data/README.textile +1 -1
- data/active-fedora.gemspec +2 -2
- data/lib/active_fedora.rb +36 -19
- data/lib/active_fedora/associations.rb +157 -0
- data/lib/active_fedora/associations/association_collection.rb +180 -0
- data/lib/active_fedora/associations/association_proxy.rb +177 -0
- data/lib/active_fedora/associations/belongs_to_association.rb +36 -0
- data/lib/active_fedora/associations/has_many_association.rb +52 -0
- data/lib/active_fedora/attribute_methods.rb +8 -0
- data/lib/active_fedora/base.rb +76 -80
- data/lib/active_fedora/datastream.rb +0 -1
- data/lib/active_fedora/delegating.rb +53 -0
- data/lib/active_fedora/model.rb +4 -2
- data/lib/active_fedora/nested_attributes.rb +153 -0
- data/lib/active_fedora/nokogiri_datastream.rb +17 -18
- data/lib/active_fedora/reflection.rb +140 -0
- data/lib/active_fedora/relationships_helper.rb +10 -5
- data/lib/active_fedora/semantic_node.rb +146 -57
- data/lib/active_fedora/solr_service.rb +0 -7
- data/lib/active_fedora/version.rb +1 -1
- data/lib/fedora/connection.rb +75 -111
- data/lib/fedora/repository.rb +14 -28
- data/lib/ruby-fedora.rb +1 -1
- data/spec/integration/associations_spec.rb +139 -0
- data/spec/integration/nested_attribute_spec.rb +40 -0
- data/spec/integration/repository_spec.rb +9 -14
- data/spec/integration/semantic_node_spec.rb +2 -0
- data/spec/spec_helper.rb +1 -3
- data/spec/unit/active_fedora_spec.rb +2 -1
- data/spec/unit/association_proxy_spec.rb +13 -0
- data/spec/unit/base_active_model_spec.rb +61 -0
- data/spec/unit/base_delegate_spec.rb +59 -0
- data/spec/unit/base_spec.rb +45 -58
- data/spec/unit/connection_spec.rb +21 -21
- data/spec/unit/datastream_spec.rb +0 -11
- data/spec/unit/has_many_collection_spec.rb +27 -0
- data/spec/unit/model_spec.rb +1 -1
- data/spec/unit/nokogiri_datastream_spec.rb +3 -29
- data/spec/unit/repository_spec.rb +2 -2
- data/spec/unit/semantic_node_spec.rb +2 -0
- data/spec/unit/solr_service_spec.rb +0 -7
- metadata +36 -15
- data/lib/active_fedora/active_fedora_configuration_exception.rb +0 -2
- data/lib/util/class_level_inheritable_attributes.rb +0 -23
@@ -65,13 +65,6 @@ module ActiveFedora
|
|
65
65
|
return uri.gsub(/(:)/, '\\:')
|
66
66
|
end
|
67
67
|
|
68
|
-
# Escapes these characters
|
69
|
-
# + - || ! ( ) { } [ ] ^ " ~ * ? : \
|
70
|
-
# See: http://lucene.apache.org/java/2_4_0/queryparsersyntax.html#Escaping%20Special%20Characters
|
71
|
-
def self.escape_characters_for_query(value)
|
72
|
-
value.gsub(/([\#\+\-\|\{\}\^\"\~\*\:\>\<\?\(\)\[\]\+\!\\])/) {|v| "\\#{v}"}
|
73
|
-
end
|
74
|
-
|
75
68
|
|
76
69
|
end #SolrService
|
77
70
|
class SolrNotInitialized < StandardError;end
|
data/lib/fedora/connection.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require "base64"
|
2
2
|
gem 'multipart-post'
|
3
3
|
require 'net/http/post/multipart'
|
4
|
-
require 'net/http/persistent'
|
5
4
|
require 'cgi'
|
6
5
|
require "mime/types"
|
7
6
|
require 'net/http'
|
@@ -47,9 +46,6 @@ module Fedora
|
|
47
46
|
# 409 Conflict
|
48
47
|
class ResourceConflict < ClientError; end # :nodoc:
|
49
48
|
|
50
|
-
# 415 Unsupported Media Type
|
51
|
-
class UnsupportedMediaType < ClientError; end # :nodoc:
|
52
|
-
|
53
49
|
# 5xx Server Error
|
54
50
|
class ServerError < ConnectionError; end # :nodoc:
|
55
51
|
|
@@ -64,23 +60,6 @@ module Fedora
|
|
64
60
|
# This class is used by ActiveResource::Base to interface with REST
|
65
61
|
# services.
|
66
62
|
class Connection
|
67
|
-
|
68
|
-
CLASSES = [
|
69
|
-
Net::HTTP::Delete,
|
70
|
-
Net::HTTP::Get,
|
71
|
-
Net::HTTP::Head,
|
72
|
-
Net::HTTP::Post,
|
73
|
-
Net::HTTP::Put
|
74
|
-
].freeze
|
75
|
-
|
76
|
-
MIME_TYPES = {
|
77
|
-
:binary => "application/octet-stream",
|
78
|
-
:json => "application/json",
|
79
|
-
:xml => "text/xml",
|
80
|
-
:none => "text/plain"
|
81
|
-
}.freeze
|
82
|
-
|
83
|
-
|
84
63
|
attr_reader :site, :surrogate
|
85
64
|
attr_accessor :format
|
86
65
|
|
@@ -99,102 +78,84 @@ module Fedora
|
|
99
78
|
@surrogate=surrogate
|
100
79
|
end
|
101
80
|
|
102
|
-
##
|
103
|
-
# Perform an HTTP Delete, Head, Get, Post, or Put.
|
104
|
-
|
105
|
-
CLASSES.each do |clazz|
|
106
|
-
verb = clazz.to_s.split("::").last.downcase
|
107
|
-
|
108
|
-
define_method verb do |*args|
|
109
|
-
path = args[0]
|
110
|
-
params = args[1] || {}
|
111
|
-
|
112
|
-
response_for clazz, path, params
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
81
|
# Set URI for remote service.
|
117
82
|
def site=(site)
|
118
83
|
@site = site.is_a?(URI) ? site : URI.parse(site)
|
119
84
|
end
|
120
85
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
def response_for(clazz, path, params)
|
126
|
-
logger.debug "#{clazz} #{path}"
|
127
|
-
request = clazz.new path
|
128
|
-
request.body = params[:body]
|
129
|
-
|
130
|
-
handle_request request, params[:upload], params[:type], params[:headers] || {}
|
86
|
+
# Execute a GET request.
|
87
|
+
# Used to get (find) resources.
|
88
|
+
def get(path, headers = {})
|
89
|
+
format.decode(request(:get, path, build_request_headers(headers)).body)
|
131
90
|
end
|
132
91
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
result = http.request self.site, request
|
138
|
-
handle_response(result)
|
92
|
+
# Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
|
93
|
+
# Used to delete resources.
|
94
|
+
def delete(path, headers = {})
|
95
|
+
request(:delete, path, build_request_headers(headers))
|
139
96
|
end
|
140
97
|
|
141
|
-
##
|
142
|
-
# Handle chunked uploads.
|
143
|
-
#
|
144
|
-
# +request+: A Net::HTTP request Object.
|
145
|
-
# +upload+: A Hash with the following keys:
|
146
|
-
# +:file+: The file to be HTTP chunked uploaded.
|
147
|
-
# +:headers+: A Hash containing additional HTTP headers.
|
148
|
-
# +:type+: A Symbol with the mime_type.
|
149
|
-
|
150
|
-
def handle_uploads request, upload, type
|
151
|
-
return unless upload
|
152
|
-
io = nil
|
153
|
-
if upload[:file].is_a?(File)
|
154
|
-
io = File.open upload[:file].path
|
155
|
-
else
|
156
|
-
io = upload[:file]
|
157
|
-
end
|
158
|
-
|
159
|
-
request.body_stream = io
|
160
|
-
end
|
161
98
|
|
162
99
|
|
163
|
-
def
|
164
|
-
request
|
100
|
+
def raw_get(path, headers = {})
|
101
|
+
request(:get, path, build_request_headers(headers))
|
102
|
+
end
|
103
|
+
def post(path, body='', headers={})
|
104
|
+
do_method(:post, path, body, headers)
|
105
|
+
end
|
106
|
+
def put( path, body='', headers={})
|
107
|
+
do_method(:put, path, body, headers)
|
108
|
+
end
|
165
109
|
|
166
|
-
|
167
|
-
|
110
|
+
private
|
111
|
+
def do_method(method, path, body = '', headers = {})
|
112
|
+
meth_map={:put=>Net::HTTP::Put::Multipart, :post=>Net::HTTP::Post::Multipart}
|
113
|
+
raise "undefined method: #{method}" unless meth_map.has_key? method
|
114
|
+
headers = build_request_headers(headers)
|
115
|
+
if body.respond_to?(:read)
|
116
|
+
if body.respond_to?(:original_filename?)
|
117
|
+
filename = File.basename(body.original_filename)
|
118
|
+
io = UploadIO.new(body, mime_type,filename)
|
119
|
+
elsif body.path
|
120
|
+
filename = File.basename(body.path)
|
121
|
+
else
|
122
|
+
filename="NOFILE"
|
123
|
+
end
|
124
|
+
mime_types = MIME::Types.of(filename)
|
125
|
+
mime_type ||= mime_types.empty? ? "application/octet-stream" : mime_types.first.content_type
|
126
|
+
|
127
|
+
io = nil
|
128
|
+
if body.is_a?(File)
|
129
|
+
io = UploadIO.new(body.path,mime_type)
|
130
|
+
else
|
131
|
+
io =UploadIO.new(body, mime_type, filename)
|
132
|
+
end
|
168
133
|
|
169
|
-
|
170
|
-
|
171
|
-
|
134
|
+
req = meth_map[method].new(path, {:file=>io}, headers)
|
135
|
+
multipart_request(req)
|
136
|
+
else
|
137
|
+
request(method, path, body.to_s, headers)
|
172
138
|
end
|
173
139
|
end
|
140
|
+
def multipart_request(req)
|
141
|
+
result = nil
|
142
|
+
result = http.start do |conn|
|
143
|
+
conn.read_timeout=60600 #these can take a while
|
144
|
+
conn.request(req)
|
145
|
+
end
|
146
|
+
handle_response(result)
|
174
147
|
|
175
|
-
##
|
176
|
-
# Setting of chunked upload headers.
|
177
|
-
#
|
178
|
-
# +upload+: A Hash with the following keys:
|
179
|
-
# +:file+: The file to be HTTP chunked uploaded.
|
180
|
-
|
181
|
-
def chunked_headers upload
|
182
|
-
return {} unless upload
|
183
|
-
|
184
|
-
chunked_headers = {
|
185
|
-
"Content-Type" => mime_type(:binary),
|
186
|
-
"Transfer-Encoding" => "chunked"
|
187
|
-
}.merge upload[:headers] || {}
|
188
148
|
end
|
189
149
|
|
190
|
-
|
191
|
-
|
150
|
+
# Makes request to remote service.
|
151
|
+
def request(method, path, *arguments)
|
152
|
+
result = http.send(method, path, *arguments)
|
153
|
+
handle_response(result)
|
192
154
|
end
|
193
155
|
|
194
156
|
# Handles response and error codes from remote service.
|
195
157
|
def handle_response(response)
|
196
158
|
message = "Error from Fedora: #{response.body}"
|
197
|
-
logger.debug "Response: #{response.code}"
|
198
159
|
case response.code.to_i
|
199
160
|
when 301,302
|
200
161
|
raise(Redirection.new(response))
|
@@ -212,8 +173,6 @@ module Fedora
|
|
212
173
|
raise(MethodNotAllowed.new(response, message))
|
213
174
|
when 409
|
214
175
|
raise(ResourceConflict.new(response, message))
|
215
|
-
when 415
|
216
|
-
raise UnsupportedMediaType.new(response, message)
|
217
176
|
when 422
|
218
177
|
raise(ResourceInvalid.new(response, message))
|
219
178
|
when 423...500
|
@@ -225,30 +184,35 @@ module Fedora
|
|
225
184
|
end
|
226
185
|
end
|
227
186
|
|
228
|
-
def mime_type type
|
229
|
-
if type.kind_of? String
|
230
|
-
type
|
231
|
-
else
|
232
|
-
MIME_TYPES[type] || MIME_TYPES[:xml]
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
187
|
# Creates new Net::HTTP instance for communication with
|
237
188
|
# remote service and resources.
|
238
189
|
def http
|
239
|
-
|
240
|
-
@http = Net::HTTP::Persistent.new#(@site)
|
190
|
+
http = Net::HTTP.new(@site.host, @site.port)
|
241
191
|
if(@site.is_a?(URI::HTTPS))
|
242
|
-
|
243
|
-
|
192
|
+
http.use_ssl = true
|
193
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
244
194
|
|
245
195
|
if (defined?(SSL_CLIENT_CERT_FILE) && !SSL_CLIENT_CERT_FILE.nil? && defined?(SSL_CLIENT_KEY_FILE) && !SSL_CLIENT_KEY_FILE.nil? && defined?(SSL_CLIENT_KEY_PASS) && !SSL_CLIENT_KEY_PASS.nil?)
|
246
|
-
|
247
|
-
|
196
|
+
http.cert = OpenSSL::X509::Certificate.new( File.read(SSL_CLIENT_CERT_FILE) )
|
197
|
+
http.key = OpenSSL::PKey::RSA.new( File.read(SSL_CLIENT_KEY_FILE), SSL_CLIENT_KEY_PASS )
|
248
198
|
end
|
249
199
|
end
|
250
|
-
|
200
|
+
http
|
251
201
|
end
|
252
202
|
|
203
|
+
def default_header
|
204
|
+
@default_header ||= { 'Content-Type' => format.mime_type }
|
205
|
+
end
|
206
|
+
|
207
|
+
# Builds headers for request to remote service.
|
208
|
+
def build_request_headers(headers)
|
209
|
+
headers.merge!({"From"=>surrogate}) if @surrogate
|
210
|
+
authorization_header.update(default_header).update(headers)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Sets authorization header; authentication information is pulled from credentials provided with site URI.
|
214
|
+
def authorization_header
|
215
|
+
(@site.user || @site.password ? { 'Authorization' => 'Basic ' + ["#{@site.user}:#{ @site.password}"].pack('m').delete("\r\n") } : {})
|
216
|
+
end
|
253
217
|
end
|
254
218
|
end
|
data/lib/fedora/repository.rb
CHANGED
@@ -59,7 +59,7 @@ module Fedora
|
|
59
59
|
|
60
60
|
# Fetch the raw content of either a fedora object or datastream
|
61
61
|
def fetch_content(object_uri)
|
62
|
-
response = connection.
|
62
|
+
response = connection.raw_get("#{url_for(object_uri)}?format=xml")
|
63
63
|
StringResponse.new(response.body, response.content_type)
|
64
64
|
end
|
65
65
|
|
@@ -95,7 +95,7 @@ module Fedora
|
|
95
95
|
params[:sessionToken] = options[:sessionToken] if options[:sessionToken]
|
96
96
|
includes = fields.inject("") { |s, f| s += "&#{f}=true"; s }
|
97
97
|
|
98
|
-
convert_xml(
|
98
|
+
convert_xml(connection.get("#{fedora_url.path}/objects?#{params.to_fedora_query}#{includes}"))
|
99
99
|
end
|
100
100
|
|
101
101
|
# Retrieve an object from fedora and load it as an instance of the given model/class
|
@@ -103,14 +103,10 @@ module Fedora
|
|
103
103
|
# @param pid of the Fedora object to retrieve and deserialize
|
104
104
|
# @param klazz the Model whose deserialize method the object's FOXML will be passed into
|
105
105
|
def find_model(pid, klazz)
|
106
|
-
obj =
|
107
|
-
|
108
|
-
|
109
|
-
if obj.nil?
|
110
|
-
raise ActiveFedora::ObjectNotFoundError, "The repository does not have an object with pid #{pid}. The repository URL is #{self.base_url}"
|
111
|
-
end
|
106
|
+
obj = self.find_objects("pid=#{pid}").first
|
107
|
+
if obj.nil?
|
108
|
+
raise ActiveFedora::ObjectNotFoundError, "The repository does not have an object with pid #{pid}. The repository URL is #{self.base_url}"
|
112
109
|
end
|
113
|
-
logger.debug "loading #{pid} took #{ms} ms"
|
114
110
|
doc = Nokogiri::XML::Document.parse(obj.object_xml)
|
115
111
|
klazz.deserialize(doc)
|
116
112
|
end
|
@@ -136,7 +132,7 @@ module Fedora
|
|
136
132
|
case object
|
137
133
|
when Fedora::FedoraObject
|
138
134
|
pid = (object.pid ? object : 'new')
|
139
|
-
response = connection.post("#{url_for(pid)}?" + object.attributes.to_fedora_query,
|
135
|
+
response = connection.post("#{url_for(pid)}?" + object.attributes.to_fedora_query, object.blob)
|
140
136
|
if response.code == '201'
|
141
137
|
object.pid = extract_pid(response)
|
142
138
|
object.new_object = false
|
@@ -146,14 +142,10 @@ module Fedora
|
|
146
142
|
end
|
147
143
|
when Fedora::Datastream
|
148
144
|
raise ArgumentError, "Missing dsID attribute" if object.dsid.nil?
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
else
|
154
|
-
headers[:body]=object.blob
|
155
|
-
end
|
156
|
-
response = connection.post("#{url_for(object)}?" + object.attributes.to_fedora_query, headers)
|
145
|
+
extra_headers = {}
|
146
|
+
extra_headers['Content-Type'] = object.attributes[:mimeType] if object.attributes[:mimeType]
|
147
|
+
response = connection.post("#{url_for(object)}?" + object.attributes.to_fedora_query,
|
148
|
+
object.blob, extra_headers)
|
157
149
|
if response.code == '201'
|
158
150
|
object.new_object = false
|
159
151
|
true
|
@@ -178,13 +170,7 @@ module Fedora
|
|
178
170
|
response.code == '200' || '307'
|
179
171
|
when Fedora::Datastream
|
180
172
|
raise ArgumentError, "Missing dsID attribute" if object.dsid.nil?
|
181
|
-
|
182
|
-
if object.blob.respond_to? :read
|
183
|
-
headers[:upload] = {:file=>object.blob}
|
184
|
-
else
|
185
|
-
headers[:body]=object.blob
|
186
|
-
end
|
187
|
-
response = connection.put("#{url_for(object)}?" + object.attributes.to_fedora_query, headers)
|
173
|
+
response = connection.put("#{url_for(object)}?" + object.attributes.to_fedora_query, object.blob)
|
188
174
|
response.code == '200' || '201'
|
189
175
|
return response.code
|
190
176
|
else
|
@@ -239,7 +225,7 @@ module Fedora
|
|
239
225
|
content_to_ingest = content_to_ingest.read
|
240
226
|
end
|
241
227
|
|
242
|
-
connection.post(url,
|
228
|
+
connection.post(url,content_to_ingest)
|
243
229
|
end
|
244
230
|
|
245
231
|
# Fetch the given object using custom method. This is used to fetch other aspects of a fedora object,
|
@@ -259,11 +245,11 @@ module Fedora
|
|
259
245
|
end
|
260
246
|
|
261
247
|
extra_params.delete(:format) if method == :export
|
262
|
-
connection.
|
248
|
+
connection.raw_get("#{url_for(object)}#{path}?#{extra_params.to_fedora_query}").body
|
263
249
|
end
|
264
250
|
|
265
251
|
def describe_repository
|
266
|
-
result_body = connection.
|
252
|
+
result_body = connection.raw_get("#{fedora_url.path}/describe?xml=true").body
|
267
253
|
XmlSimple.xml_in(result_body)
|
268
254
|
end
|
269
255
|
|
data/lib/ruby-fedora.rb
CHANGED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Library < ActiveFedora::Base
|
4
|
+
has_many :books, :property=>:has_constituent
|
5
|
+
end
|
6
|
+
|
7
|
+
class Book < ActiveFedora::Base
|
8
|
+
belongs_to :library, :property=>:has_constituent
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ActiveFedora::Base do
|
12
|
+
describe "an unsaved instance" do
|
13
|
+
before do
|
14
|
+
@library = Library.new()
|
15
|
+
@book = Book.new
|
16
|
+
@book.save
|
17
|
+
@book2 = Book.new
|
18
|
+
@book2.save
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should let you shift onto the association" do
|
22
|
+
@library.new_record?.should be_true
|
23
|
+
@library.books.size == 0
|
24
|
+
@library.books.to_ary.should == []
|
25
|
+
@library.book_ids.should ==[]
|
26
|
+
@library.books << @book
|
27
|
+
@library.books.map(&:pid).should == [@book.pid]
|
28
|
+
@library.book_ids.should ==[@book.pid]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should let you set an array of objects" do
|
32
|
+
@library.books = [@book, @book2]
|
33
|
+
@library.books.map(&:pid).should == [@book.pid, @book2.pid]
|
34
|
+
@library.save
|
35
|
+
|
36
|
+
@library.books = [@book]
|
37
|
+
@library.books.map(&:pid).should == [@book.pid]
|
38
|
+
|
39
|
+
end
|
40
|
+
it "should let you set an array of object ids" do
|
41
|
+
@library.book_ids = [@book.pid, @book2.pid]
|
42
|
+
@library.books.map(&:pid).should == [@book.pid, @book2.pid]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "setter should wipe out previously saved relations" do
|
46
|
+
@library.book_ids = [@book.pid, @book2.pid]
|
47
|
+
@library.book_ids = [@book2.pid]
|
48
|
+
@library.books.map(&:pid).should == [@book2.pid]
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
after do
|
53
|
+
@book.delete
|
54
|
+
@book2.delete
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
describe "a saved instance" do
|
60
|
+
before do
|
61
|
+
@library = Library.new()
|
62
|
+
@library.save()
|
63
|
+
@book = Book.new
|
64
|
+
@book.save
|
65
|
+
end
|
66
|
+
it "should have many books once it has been saved" do
|
67
|
+
@library.save
|
68
|
+
@library.books << @book
|
69
|
+
|
70
|
+
@book.library.pid.should == @library.pid
|
71
|
+
@library.books.reload
|
72
|
+
@library.books.map(&:pid).should == [@book.pid]
|
73
|
+
|
74
|
+
|
75
|
+
@library2 = Library.find(@library.pid)
|
76
|
+
@library2.books.map(&:pid).should == [@book.pid]
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
after do
|
81
|
+
@library.delete
|
82
|
+
@book.delete
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "setting belongs_to" do
|
87
|
+
before do
|
88
|
+
@library = Library.new()
|
89
|
+
@library.save()
|
90
|
+
@book = Book.new
|
91
|
+
end
|
92
|
+
it "should set the association" do
|
93
|
+
@book.library = @library
|
94
|
+
@book.library.pid.should == @library.pid
|
95
|
+
@book.save
|
96
|
+
|
97
|
+
|
98
|
+
Book.find(@book.pid).library.pid.should == @library.pid
|
99
|
+
|
100
|
+
end
|
101
|
+
it "should clear the association" do
|
102
|
+
@book.library = @library
|
103
|
+
@book.library = nil
|
104
|
+
@book.save
|
105
|
+
|
106
|
+
Book.find(@book.pid).library.should be_nil
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should replace the association" do
|
111
|
+
@library2 = Library.new
|
112
|
+
@library2.save
|
113
|
+
@book.library = @library
|
114
|
+
@book.save
|
115
|
+
@book.library = @library2
|
116
|
+
@book.save
|
117
|
+
Book.find(@book.pid).library.pid.should == @library2.pid
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should be able to be set by id" do
|
122
|
+
@book.library_id = @library.pid
|
123
|
+
@book.library_id.should == @library.pid
|
124
|
+
@book.library.pid.should == @library.pid
|
125
|
+
@book.save
|
126
|
+
Book.find(@book.pid).library_id.should == @library.pid
|
127
|
+
end
|
128
|
+
|
129
|
+
after do
|
130
|
+
@library.delete
|
131
|
+
@book.delete
|
132
|
+
@library2.delete if @library2
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
|
138
|
+
|
139
|
+
end
|