active-fedora 2.3.8 → 3.0.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/.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
|