her 0.7 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -2
- data/lib/her/model.rb +1 -0
- data/lib/her/model/associations/has_many_association.rb +4 -1
- data/lib/her/model/http.rb +1 -1
- data/lib/her/model/parse.rb +88 -23
- data/lib/her/version.rb +1 -1
- data/spec/model/associations_spec.rb +7 -1
- data/spec/model/nested_attributes_spec.rb +28 -0
- data/spec/model/parse_spec.rb +89 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7626abb2e0aa83555379ffb426c47f766e7e5175
|
4
|
+
data.tar.gz: 9036b8f208b4e9462740eb22fa1b6d21ef48b9c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6a8734624af88d8261d6f26147c517626530c925f886770b284471dde1f9903c455dd77a1dd6e7d21fc8a720d98d293fed0f3f967ccc3516dc05a1528a58b70
|
7
|
+
data.tar.gz: 4badd683f1cc2550dc34758573c6a57b46843b52b1a12b213eb917b6b2c60542cb05ff47038c9cc5aa8d497571fbf8569432a7d56024e7109c83fcf200dedc5b
|
data/README.md
CHANGED
@@ -576,10 +576,28 @@ class User
|
|
576
576
|
end
|
577
577
|
|
578
578
|
user = Users.find(1)
|
579
|
-
# GET "/users/1", response is { "user": { "id": 1, "fullname": "Lindsay Fünke"} }
|
579
|
+
# GET "/users/1", response is { "user": { "id": 1, "fullname": "Lindsay Fünke" } }
|
580
580
|
|
581
581
|
users = Users.all
|
582
|
-
# GET "/users", response is { "users": [{ "id": 1, "fullname": "Lindsay Fünke"}] }
|
582
|
+
# GET "/users", response is { "users": [{ "id": 1, "fullname": "Lindsay Fünke" }, { "id": 1, "fullname": "Tobias Fünke" }] }
|
583
|
+
```
|
584
|
+
|
585
|
+
#### JSON API support
|
586
|
+
|
587
|
+
If the API returns data in the [JSON API format](http://jsonapi.org/) you need
|
588
|
+
to configure Her as follows:
|
589
|
+
|
590
|
+
```ruby
|
591
|
+
class User
|
592
|
+
include Her::Model
|
593
|
+
parse_root_in_json true, format: :json_api
|
594
|
+
end
|
595
|
+
|
596
|
+
user = Users.find(1)
|
597
|
+
# GET "/users/1", response is { "users": [{ "id": 1, "fullname": "Lindsay Fünke" }] }
|
598
|
+
|
599
|
+
users = Users.all
|
600
|
+
# GET "/users", response is { "users": [{ "id": 1, "fullname": "Lindsay Fünke" }, { "id": 2, "fullname": "Tobias Fünke" }] }
|
583
601
|
```
|
584
602
|
|
585
603
|
### Custom requests
|
@@ -964,6 +982,8 @@ These [fine folks](https://github.com/remiprev/her/contributors) helped with Her
|
|
964
982
|
* [@aclevy](https://github.com/aclevy)
|
965
983
|
* [@stevschmid](https://github.com/stevschmid)
|
966
984
|
* [@prognostikos](https://github.com/prognostikos)
|
985
|
+
* [@dturnerTS](https://github.com/dturnerTS)
|
986
|
+
* [@kritik](https://github.com/kritik)
|
967
987
|
|
968
988
|
## License
|
969
989
|
|
data/lib/her/model.rb
CHANGED
@@ -40,6 +40,7 @@ module Her
|
|
40
40
|
# Supported ActiveModel modules
|
41
41
|
include ActiveModel::AttributeMethods
|
42
42
|
include ActiveModel::Validations
|
43
|
+
include ActiveModel::Validations::Callbacks
|
43
44
|
include ActiveModel::Conversion
|
44
45
|
include ActiveModel::Dirty
|
45
46
|
include ActiveModel::Naming
|
@@ -49,6 +49,8 @@ module Her
|
|
49
49
|
# user = User.find(1)
|
50
50
|
# new_comment = user.comments.build(:body => "Hello!")
|
51
51
|
# new_comment # => #<Comment user_id=1 body="Hello!">
|
52
|
+
# TODO: This only merges the id of the parents, handle the case
|
53
|
+
# where this is more deeply nested
|
52
54
|
def build(attributes = {})
|
53
55
|
@klass.build(attributes.merge(:"#{@parent.singularized_resource_name}_id" => @parent.id))
|
54
56
|
end
|
@@ -89,7 +91,8 @@ module Her
|
|
89
91
|
|
90
92
|
# @private
|
91
93
|
def assign_nested_attributes(attributes)
|
92
|
-
|
94
|
+
data = attributes.is_a?(Hash) ? attributes.values : attributes
|
95
|
+
@parent.attributes[@name] = Her::Model::Attributes.initialize_collection(@klass, :data => data)
|
93
96
|
end
|
94
97
|
end
|
95
98
|
end
|
data/lib/her/model/http.rb
CHANGED
@@ -68,7 +68,7 @@ module Her
|
|
68
68
|
path = build_request_path_from_string_or_symbol(path, params)
|
69
69
|
params = to_params(params) unless #{method.to_sym.inspect} == :get
|
70
70
|
send(:'#{method}_raw', path, params) do |parsed_data, response|
|
71
|
-
if parsed_data[:data].is_a?(Array) || active_model_serializers_format?
|
71
|
+
if parsed_data[:data].is_a?(Array) || active_model_serializers_format? || json_api_format?
|
72
72
|
new_collection(parsed_data)
|
73
73
|
else
|
74
74
|
new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
|
data/lib/her/model/parse.rb
CHANGED
@@ -19,19 +19,54 @@ module Her
|
|
19
19
|
# @param [Hash] data
|
20
20
|
# @private
|
21
21
|
def parse(data)
|
22
|
-
parse_root_in_json? ?
|
22
|
+
if parse_root_in_json? && root_element_included?(data)
|
23
|
+
if json_api_format?
|
24
|
+
data.fetch(parsed_root_element).first
|
25
|
+
else
|
26
|
+
data.fetch(parsed_root_element) { data }
|
27
|
+
end
|
28
|
+
else
|
29
|
+
data
|
30
|
+
end
|
23
31
|
end
|
24
32
|
|
25
33
|
# @private
|
26
34
|
def to_params(attributes, changes={})
|
27
35
|
filtered_attributes = attributes.dup.symbolize_keys
|
36
|
+
filtered_attributes.merge!(embeded_params(attributes))
|
28
37
|
if her_api.options[:send_only_modified_attributes]
|
29
38
|
filtered_attributes = changes.symbolize_keys.keys.inject({}) do |hash, attribute|
|
30
39
|
hash[attribute] = filtered_attributes[attribute]
|
31
40
|
hash
|
32
41
|
end
|
33
42
|
end
|
34
|
-
|
43
|
+
|
44
|
+
if include_root_in_json?
|
45
|
+
if json_api_format?
|
46
|
+
{ included_root_element => [filtered_attributes] }
|
47
|
+
else
|
48
|
+
{ included_root_element => filtered_attributes }
|
49
|
+
end
|
50
|
+
else
|
51
|
+
filtered_attributes
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# @private
|
57
|
+
# TODO: Handle has_one
|
58
|
+
def embeded_params(attributes)
|
59
|
+
associations[:has_many].select { |a| attributes.include?(a[:data_key])}.compact.inject({}) do |hash, association|
|
60
|
+
params = attributes[association[:data_key]].map(&:to_params)
|
61
|
+
next if params.empty?
|
62
|
+
if association[:class_name].constantize.include_root_in_json?
|
63
|
+
root = association[:class_name].constantize.root_element
|
64
|
+
hash[association[:data_key]] = params.map { |n| n[root] }
|
65
|
+
else
|
66
|
+
hash[association[:data_key]] = params
|
67
|
+
end
|
68
|
+
hash
|
69
|
+
end
|
35
70
|
end
|
36
71
|
|
37
72
|
# Return or change the value of `include_root_in_json`
|
@@ -41,30 +76,33 @@ module Her
|
|
41
76
|
# include Her::Model
|
42
77
|
# include_root_in_json true
|
43
78
|
# end
|
44
|
-
def include_root_in_json(value)
|
79
|
+
def include_root_in_json(value, options = {})
|
45
80
|
@_her_include_root_in_json = value
|
81
|
+
@_her_include_root_in_json_format = options[:format]
|
46
82
|
end
|
47
83
|
|
48
|
-
|
49
|
-
@_her_include_root_in_json || (superclass.respond_to?(:include_root_in_json?) && superclass.include_root_in_json?)
|
50
|
-
end
|
51
|
-
|
52
|
-
# Return or change the value of `parse_root_in`
|
84
|
+
# Return or change the value of `parse_root_in_json`
|
53
85
|
#
|
54
86
|
# @example
|
55
87
|
# class User
|
56
88
|
# include Her::Model
|
57
89
|
# parse_root_in_json true
|
58
90
|
# end
|
91
|
+
#
|
92
|
+
# class User
|
93
|
+
# include Her::Model
|
94
|
+
# parse_root_in_json true, format: :active_model_serializers
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# class User
|
98
|
+
# include Her::Model
|
99
|
+
# parse_root_in_json true, format: :json_api
|
100
|
+
# end
|
59
101
|
def parse_root_in_json(value, options = {})
|
60
102
|
@_her_parse_root_in_json = value
|
61
103
|
@_her_parse_root_in_json_format = options[:format]
|
62
104
|
end
|
63
105
|
|
64
|
-
def parse_root_in_json?
|
65
|
-
@_her_parse_root_in_json || (superclass.respond_to?(:parse_root_in_json?) && superclass.parse_root_in_json?)
|
66
|
-
end
|
67
|
-
|
68
106
|
# Return or change the value of `request_new_object_on_build`
|
69
107
|
#
|
70
108
|
# @example
|
@@ -76,10 +114,6 @@ module Her
|
|
76
114
|
@_her_request_new_object_on_build = value
|
77
115
|
end
|
78
116
|
|
79
|
-
def request_new_object_on_build?
|
80
|
-
@_her_request_new_object_on_build || (superclass.respond_to?(:request_new_object_on_build?) && superclass.request_new_object_on_build?)
|
81
|
-
end
|
82
|
-
|
83
117
|
# Return or change the value of `root_element`. Always defaults to the base name of the class.
|
84
118
|
#
|
85
119
|
# @example
|
@@ -93,12 +127,26 @@ module Her
|
|
93
127
|
# user.name # => "Tobias"
|
94
128
|
def root_element(value = nil)
|
95
129
|
if value.nil?
|
96
|
-
|
130
|
+
if json_api_format?
|
131
|
+
@_her_root_element ||= self.name.split("::").last.pluralize.underscore.to_sym
|
132
|
+
else
|
133
|
+
@_her_root_element ||= self.name.split("::").last.underscore.to_sym
|
134
|
+
end
|
97
135
|
else
|
98
136
|
@_her_root_element = value.to_sym
|
99
137
|
end
|
100
138
|
end
|
101
139
|
|
140
|
+
# @private
|
141
|
+
def root_element_included?(data)
|
142
|
+
data.keys.to_s.include? @_her_root_element.to_s
|
143
|
+
end
|
144
|
+
|
145
|
+
# @private
|
146
|
+
def included_root_element
|
147
|
+
include_root_in_json? == true ? root_element : include_root_in_json?
|
148
|
+
end
|
149
|
+
|
102
150
|
# Extract an array from the request data
|
103
151
|
#
|
104
152
|
# @example
|
@@ -118,8 +166,10 @@ module Her
|
|
118
166
|
#
|
119
167
|
# users = User.all # [ { :id => 1, :name => "Tobias" } ]
|
120
168
|
# users.first.name # => "Tobias"
|
169
|
+
#
|
170
|
+
# @private
|
121
171
|
def extract_array(request_data)
|
122
|
-
if active_model_serializers_format?
|
172
|
+
if active_model_serializers_format? || json_api_format?
|
123
173
|
request_data[:data][pluralized_parsed_root_element]
|
124
174
|
else
|
125
175
|
request_data[:data]
|
@@ -131,11 +181,6 @@ module Her
|
|
131
181
|
parsed_root_element.to_s.pluralize.to_sym
|
132
182
|
end
|
133
183
|
|
134
|
-
# @private
|
135
|
-
def included_root_element
|
136
|
-
include_root_in_json? == true ? root_element : include_root_in_json?
|
137
|
-
end
|
138
|
-
|
139
184
|
# @private
|
140
185
|
def parsed_root_element
|
141
186
|
parse_root_in_json? == true ? root_element : parse_root_in_json?
|
@@ -145,6 +190,26 @@ module Her
|
|
145
190
|
def active_model_serializers_format?
|
146
191
|
@_her_parse_root_in_json_format == :active_model_serializers || (superclass.respond_to?(:active_model_serializers_format?) && superclass.active_model_serializers_format?)
|
147
192
|
end
|
193
|
+
|
194
|
+
# @private
|
195
|
+
def json_api_format?
|
196
|
+
@_her_parse_root_in_json_format == :json_api || (superclass.respond_to?(:json_api_format?) && superclass.json_api_format?)
|
197
|
+
end
|
198
|
+
|
199
|
+
# @private
|
200
|
+
def request_new_object_on_build?
|
201
|
+
@_her_request_new_object_on_build || (superclass.respond_to?(:request_new_object_on_build?) && superclass.request_new_object_on_build?)
|
202
|
+
end
|
203
|
+
|
204
|
+
# @private
|
205
|
+
def include_root_in_json?
|
206
|
+
@_her_include_root_in_json || (superclass.respond_to?(:include_root_in_json?) && superclass.include_root_in_json?)
|
207
|
+
end
|
208
|
+
|
209
|
+
# @private
|
210
|
+
def parse_root_in_json?
|
211
|
+
@_her_parse_root_in_json || (superclass.respond_to?(:parse_root_in_json?) && superclass.parse_root_in_json?)
|
212
|
+
end
|
148
213
|
end
|
149
214
|
end
|
150
215
|
end
|
data/lib/her/version.rb
CHANGED
@@ -111,7 +111,7 @@ describe Her::Model::Associations do
|
|
111
111
|
end
|
112
112
|
|
113
113
|
spawn_model "Foo::User" do
|
114
|
-
has_many :comments
|
114
|
+
has_many :comments, class_name: "Foo::Comment"
|
115
115
|
has_one :role
|
116
116
|
belongs_to :organization
|
117
117
|
has_many :posts, :inverse_of => :admin
|
@@ -222,6 +222,12 @@ describe Her::Model::Associations do
|
|
222
222
|
@user_without_included_data.organization.should_not be_empty
|
223
223
|
end
|
224
224
|
|
225
|
+
it 'includes has_many relationships in params by default' do
|
226
|
+
params = @user_with_included_data.to_params
|
227
|
+
params[:comments].should be_kind_of(Array)
|
228
|
+
params[:comments].length.should eq(2)
|
229
|
+
end
|
230
|
+
|
225
231
|
[:create, :save_existing, :destroy].each do |type|
|
226
232
|
context "after #{type}" do
|
227
233
|
let(:subject) { self.send("user_with_included_data_after_#{type}")}
|
@@ -96,6 +96,34 @@ describe Her::Model::NestedAttributes do
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
+
context "with a has_many association as a Hash" do
|
100
|
+
before do
|
101
|
+
Her::API.setup :url => "https://api.example.com" do |builder|
|
102
|
+
builder.use Her::Middleware::FirstLevelParseJSON
|
103
|
+
builder.use Faraday::Request::UrlEncoded
|
104
|
+
end
|
105
|
+
|
106
|
+
spawn_model "Foo::User" do
|
107
|
+
has_many :pets
|
108
|
+
accepts_nested_attributes_for :pets
|
109
|
+
end
|
110
|
+
|
111
|
+
spawn_model "Foo::Pet"
|
112
|
+
|
113
|
+
@user_with_data_through_nested_attributes_as_hash = Foo::User.new :name => "Test", :pets_attributes => { '0' => { :name => "Hasi" }, '1' => { :name => "Rodriguez" }}
|
114
|
+
end
|
115
|
+
|
116
|
+
context "when children do not yet exist" do
|
117
|
+
it "creates an instance of the associated class" do
|
118
|
+
@user_with_data_through_nested_attributes_as_hash.pets.length.should == 2
|
119
|
+
@user_with_data_through_nested_attributes_as_hash.pets[0].should be_a(Foo::Pet)
|
120
|
+
@user_with_data_through_nested_attributes_as_hash.pets[1].should be_a(Foo::Pet)
|
121
|
+
@user_with_data_through_nested_attributes_as_hash.pets[0].name.should == "Hasi"
|
122
|
+
@user_with_data_through_nested_attributes_as_hash.pets[1].name.should == "Rodriguez"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
99
127
|
context "with an unknown association" do
|
100
128
|
it "raises an error" do
|
101
129
|
expect {
|
data/spec/model/parse_spec.rb
CHANGED
@@ -231,6 +231,95 @@ describe Her::Model::Parse do
|
|
231
231
|
end
|
232
232
|
end
|
233
233
|
|
234
|
+
context "when parse_root_in_json set json_api to true" do
|
235
|
+
before do
|
236
|
+
Her::API.setup :url => "https://api.example.com" do |builder|
|
237
|
+
builder.use Her::Middleware::FirstLevelParseJSON
|
238
|
+
builder.use Faraday::Request::UrlEncoded
|
239
|
+
builder.adapter :test do |stub|
|
240
|
+
stub.get("/users") { |env| [200, {}, { :users => [{ :id => 1, :fullname => "Lindsay Fünke" }] }.to_json] }
|
241
|
+
stub.get("/users/admins") { |env| [200, {}, { :users => [{ :id => 1, :fullname => "Lindsay Fünke" }] }.to_json] }
|
242
|
+
stub.get("/users/1") { |env| [200, {}, { :users => [{ :id => 1, :fullname => "Lindsay Fünke" }] }.to_json] }
|
243
|
+
stub.post("/users") { |env| [200, {}, { :users => [{ :fullname => "Lindsay Fünke" }] }.to_json] }
|
244
|
+
stub.put("/users/1") { |env| [200, {}, { :users => [{ :id => 1, :fullname => "Tobias Fünke Jr." }] }.to_json] }
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
spawn_model("Foo::User") do
|
249
|
+
parse_root_in_json true, :format => :json_api
|
250
|
+
include_root_in_json true
|
251
|
+
custom_get :admins
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
it "parse the data from the JSON root element after .create" do
|
256
|
+
@new_user = Foo::User.create(:fullname => "Lindsay Fünke")
|
257
|
+
@new_user.fullname.should == "Lindsay Fünke"
|
258
|
+
end
|
259
|
+
|
260
|
+
it "parse the data from the JSON root element after an arbitrary HTTP request" do
|
261
|
+
@new_user = Foo::User.admins
|
262
|
+
@new_user.first.fullname.should == "Lindsay Fünke"
|
263
|
+
end
|
264
|
+
|
265
|
+
it "parse the data from the JSON root element after .all" do
|
266
|
+
@users = Foo::User.all
|
267
|
+
@users.first.fullname.should == "Lindsay Fünke"
|
268
|
+
end
|
269
|
+
|
270
|
+
it "parse the data from the JSON root element after .find" do
|
271
|
+
@user = Foo::User.find(1)
|
272
|
+
@user.fullname.should == "Lindsay Fünke"
|
273
|
+
end
|
274
|
+
|
275
|
+
it "parse the data from the JSON root element after .save" do
|
276
|
+
@user = Foo::User.find(1)
|
277
|
+
@user.fullname = "Tobias Fünke"
|
278
|
+
@user.save
|
279
|
+
@user.fullname.should == "Tobias Fünke Jr."
|
280
|
+
end
|
281
|
+
|
282
|
+
it "parse the data from the JSON root element after new/save" do
|
283
|
+
@user = Foo::User.new
|
284
|
+
@user.fullname = "Lindsay Fünke (before save)"
|
285
|
+
@user.save
|
286
|
+
@user.fullname.should == "Lindsay Fünke"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
context "when include_root_in_json set json_api" do
|
291
|
+
before do
|
292
|
+
Her::API.setup :url => "https://api.example.com" do |builder|
|
293
|
+
builder.use Her::Middleware::FirstLevelParseJSON
|
294
|
+
builder.use Faraday::Request::UrlEncoded
|
295
|
+
end
|
296
|
+
|
297
|
+
Her::API.default_api.connection.adapter :test do |stub|
|
298
|
+
stub.post("/users") { |env| [200, {}, { :users => [{ :id => 1, :fullname => params(env)[:users][:fullname] }] }.to_json] }
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
context "to true" do
|
303
|
+
before do
|
304
|
+
spawn_model "Foo::User" do
|
305
|
+
include_root_in_json true
|
306
|
+
parse_root_in_json true, format: :json_api
|
307
|
+
custom_post :admins
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
it "wraps params in the element name in `to_params`" do
|
312
|
+
@new_user = Foo::User.new(:fullname => "Tobias Fünke")
|
313
|
+
@new_user.to_params.should == { :users => [{ :fullname => "Tobias Fünke" }] }
|
314
|
+
end
|
315
|
+
|
316
|
+
it "wraps params in the element name in `.where`" do
|
317
|
+
@new_user = Foo::User.where(:fullname => "Tobias Fünke").build
|
318
|
+
@new_user.fullname.should == "Tobias Fünke"
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
234
323
|
context 'when send_only_modified_attributes is set' do
|
235
324
|
before do
|
236
325
|
Her::API.setup :url => "https://api.example.com", :send_only_modified_attributes => true do |builder|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: her
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rémi Prévost
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|