reactive_resource 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,20 +1,20 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reactive_resource (0.5.1)
4
+ reactive_resource (0.6.0)
5
5
  activeresource (>= 2.3.10)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
- activemodel (3.0.3)
11
- activesupport (= 3.0.3)
10
+ activemodel (3.0.6)
11
+ activesupport (= 3.0.6)
12
12
  builder (~> 2.1.2)
13
- i18n (~> 0.4)
14
- activeresource (3.0.3)
15
- activemodel (= 3.0.3)
16
- activesupport (= 3.0.3)
17
- activesupport (3.0.3)
13
+ i18n (~> 0.5.0)
14
+ activeresource (3.0.6)
15
+ activemodel (= 3.0.6)
16
+ activesupport (= 3.0.6)
17
+ activesupport (3.0.6)
18
18
  addressable (2.2.2)
19
19
  builder (2.1.2)
20
20
  crack (0.1.8)
@@ -29,7 +29,6 @@ PLATFORMS
29
29
  ruby
30
30
 
31
31
  DEPENDENCIES
32
- activeresource (>= 2.3.10)
33
32
  rake
34
33
  reactive_resource!
35
34
  shoulda (~> 2.11.3)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2011 by Justin Weiss
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -60,6 +60,7 @@ module ReactiveResource
60
60
  # address.lawyer_id = 3
61
61
  define_method("#{attribute}_id=") do |value|
62
62
  prefix_options["#{attribute}_id".intern] = value
63
+ attributes["#{attribute}_id".intern] = value
63
64
  end
64
65
 
65
66
  # address.lawyer
@@ -91,6 +91,11 @@ module ReactiveResource
91
91
  def load(attributes)
92
92
  self.class.belongs_to_with_parents.each do |belongs_to_param|
93
93
  attributes["#{belongs_to_param}_id".intern] ||= prefix_options["#{belongs_to_param}_id".intern]
94
+
95
+ # also set prefix attributes as real attributes. Otherwise,
96
+ # belongs_to attributes will be stripped out of the response
97
+ # even if we aren't actually using the association.
98
+ @attributes["#{belongs_to_param}_id".intern] = attributes["#{belongs_to_param}_id".intern]
94
99
  end
95
100
  super(attributes)
96
101
  end
@@ -109,6 +114,33 @@ module ReactiveResource
109
114
  @prefix_parameters
110
115
  end
111
116
 
117
+ # Returns a list of the belongs_to associations we will use to
118
+ # generate the full path for this resource.
119
+ def self.prefix_associations(options)
120
+ options = options.dup
121
+ used_associations = []
122
+ parent_associations = []
123
+
124
+ # Recurse to add the parent resource hierarchy. For Phone, for
125
+ # instance, this will add the '/lawyers/:id' part of the URL,
126
+ # which it knows about from the Address class.
127
+ parents.each do |parent|
128
+ parent_associations = parent.prefix_associations(options)
129
+ break unless parent_associations.empty?
130
+ end
131
+
132
+ # The association chain we're following
133
+ used_association = nil
134
+
135
+ belongs_to_associations.each do |association|
136
+ if !used_association && param_value = options.delete("#{association.attribute}_id".intern) # only take the first one
137
+ used_associations << association
138
+ break
139
+ end
140
+ end
141
+ parent_associations + used_associations
142
+ end
143
+
112
144
  # Generates the URL prefix that the belongs_to parameters and
113
145
  # associations refer to. For example, a license with params
114
146
  # :lawyer_id => 2 will return 'lawyers/2/' and a phone with params
@@ -117,23 +149,21 @@ module ReactiveResource
117
149
  def self.association_prefix(options)
118
150
  options = options.dup
119
151
  association_prefix = ''
120
- parent_prefix = ''
121
152
 
122
153
  if belongs_to_associations
123
- # Recurse to add the parent resource hierarchy. For Phone, for
124
- # instance, this will add the '/lawyers/:id' part of the URL,
125
- # which it knows about from the Address class.
126
- parents.each do |parent|
127
- parent_prefix = parent.association_prefix(options) if parent_prefix.blank?
128
- end
129
154
 
130
- belongs_to_associations.each do |association|
131
- if association_prefix.blank? && param_value = options.delete("#{association.attribute}_id".intern) # only take the first one
132
- association_prefix = "#{association.associated_class.collection_name}/#{param_value}/"
133
- end
134
- end
155
+ used_associations = prefix_associations(options)
156
+
157
+ association_prefix = used_associations.map do |association|
158
+ collection_name = association.associated_class.collection_name
159
+ value = options.delete("#{association.attribute}_id".intern)
160
+ "#{collection_name}/#{value}"
161
+ end.join('/')
162
+
163
+ # add trailing slash
164
+ association_prefix << '/' unless used_associations.empty?
135
165
  end
136
- parent_prefix + association_prefix
166
+ association_prefix
137
167
  end
138
168
 
139
169
  class << self
@@ -213,6 +243,27 @@ module ReactiveResource
213
243
  end
214
244
  end
215
245
 
246
+ # In order to support two-way belongs_to associations in a
247
+ # reasonable way, we duplicate all of the prefix options as real
248
+ # attributes (so license.lawyer_id will set both the lawyer_id
249
+ # attribute and the lawyer_id prefix option). When we're ready to
250
+ # save, we should take all the parameters that we're already
251
+ # sending as prefix options and remove them from the model's
252
+ # attributes so we don't send duplicates down the wire. This way,
253
+ # if you have an object that belongs_to :lawyer and belongs_to
254
+ # :phone (in that order), setting lawyer_id and phone_id will
255
+ # treat lawyer_id as a prefix option and phone_id as a normal
256
+ # attribute.
257
+ def save
258
+ if self.class.belongs_to_associations
259
+ used_attributes = self.class.prefix_associations(prefix_options).map {|association| association.attribute}
260
+ used_attributes.each do |attribute|
261
+ attributes.delete("#{attribute}_id".intern)
262
+ end
263
+ end
264
+ super
265
+ end
266
+
216
267
  # belongs_to in ReactiveResource works a little differently than
217
268
  # ActiveRecord. Because we have to deal with full class hierachies
218
269
  # in order to generate the full URL (as mentioned in
@@ -1,4 +1,4 @@
1
1
  module ReactiveResource
2
2
  # The current version of ReactiveResource
3
- VERSION = "0.5.1"
3
+ VERSION = "0.6.0"
4
4
  end
data/test/test_objects.rb CHANGED
@@ -28,6 +28,19 @@ class ReactiveResource::Address < ReactiveResource::Base
28
28
  has_many :phones
29
29
  end
30
30
 
31
+ class ReactiveResource::Phone < ReactiveResource::Base
32
+ belongs_to :address
33
+ end
34
+
35
+ class ReactiveResource::Post < ReactiveResource::Base
36
+ has_one :lawyer_post
37
+ end
38
+
39
+ class ReactiveResource::LawyerPost < ReactiveResource::Base
40
+ belongs_to :lawyer
41
+ belongs_to :post
42
+ end
43
+
31
44
  module ChildResource
32
45
 
33
46
  class Address < ReactiveResource::Address
@@ -39,10 +52,6 @@ module ChildResource
39
52
  end
40
53
  end
41
54
 
42
- class ReactiveResource::Phone < ReactiveResource::Base
43
- belongs_to :address
44
- end
45
-
46
55
  module ActiveResource
47
56
  module Formats
48
57
  module NoFormatFormat
@@ -166,4 +166,20 @@ class ReactiveResource::BaseTest < Test::Unit::TestCase
166
166
  assert_requested(:get, "https://api.avvo.com/api/1/no_extensions/test")
167
167
  end
168
168
  end
169
+
170
+ context "A resource with a two-way belongs to association" do
171
+ should "convert unused prefix options to attributes" do
172
+ stub_request(:post, "https://api.avvo.com/api/1/lawyers/1/lawyer_posts.json").
173
+ with(:body => {"lawyer_post" => {"post_id" => 2}})
174
+ @object = ReactiveResource::LawyerPost.new(:lawyer_id => 1, :post_id => 2).save
175
+ assert_requested(:post, "https://api.avvo.com/api/1/lawyers/1/lawyer_posts.json")
176
+ end
177
+
178
+ should "set the attributes correctly when it's created" do
179
+ @object = ReactiveResource::LawyerPost.new(:lawyer_id => 1, :post_id => 2)
180
+ assert_not_nil @object.attributes[:post_id]
181
+ assert_not_nil @object.attributes[:lawyer_id]
182
+ end
183
+ end
184
+
169
185
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 5
8
- - 1
9
- version: 0.5.1
7
+ - 6
8
+ - 0
9
+ version: 0.6.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Justin Weiss
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-01-28 00:00:00 -08:00
17
+ date: 2011-04-13 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -75,6 +75,7 @@ files:
75
75
  - .gitignore
76
76
  - Gemfile
77
77
  - Gemfile.lock
78
+ - LICENSE
78
79
  - README.rdoc
79
80
  - Rakefile
80
81
  - lib/reactive_resource.rb
@@ -104,7 +105,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
104
105
  requirements:
105
106
  - - ">="
106
107
  - !ruby/object:Gem::Version
107
- hash: 4176716010993460221
108
+ hash: -2570327779021755250
108
109
  segments:
109
110
  - 0
110
111
  version: "0"
@@ -113,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
114
  requirements:
114
115
  - - ">="
115
116
  - !ruby/object:Gem::Version
116
- hash: 4176716010993460221
117
+ hash: -2570327779021755250
117
118
  segments:
118
119
  - 0
119
120
  version: "0"