her 0.7 → 0.7.1
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.
- 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
|