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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4f5ffa339d778a9e0c1bdf8284bb6df2452584b9
4
- data.tar.gz: f37e2a93474b2290a0b676ec70c793232ad49eb7
3
+ metadata.gz: 7626abb2e0aa83555379ffb426c47f766e7e5175
4
+ data.tar.gz: 9036b8f208b4e9462740eb22fa1b6d21ef48b9c1
5
5
  SHA512:
6
- metadata.gz: 9d6670525bc4efb9c658cf9692ffda8fdb2c9ae24267f0b44c48980b39330b9b3fd51f47b73ad024f8cbb53f45ebff43d8adafe5adf6cae91f739a17aa5715bd
7
- data.tar.gz: 45e82cff66d8f55e5887769a103a8015deff104c27e0d9e1fc0daff79dd53bc9e67681437ff3b692017d7c74596a9a2b3d5cd133906db0cbec9a01a3874057bb
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
- @parent.attributes[@name] = Her::Model::Attributes.initialize_collection(@klass, :data => attributes)
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
@@ -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])
@@ -19,19 +19,54 @@ module Her
19
19
  # @param [Hash] data
20
20
  # @private
21
21
  def parse(data)
22
- parse_root_in_json? ? data.fetch(parsed_root_element) { data } : data
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
- include_root_in_json? ? { included_root_element => filtered_attributes } : filtered_attributes
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
- def include_root_in_json?
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
- @_her_root_element ||= self.name.split("::").last.underscore.to_sym
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
@@ -1,3 +1,3 @@
1
1
  module Her
2
- VERSION = "0.7"
2
+ VERSION = "0.7.1"
3
3
  end
@@ -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 {
@@ -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: '0.7'
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-03 00:00:00.000000000 Z
11
+ date: 2014-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake