her 0.8.2 → 0.9.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.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/.rubocop.yml +1291 -0
- data/.travis.yml +2 -0
- data/README.md +23 -3
- data/her.gemspec +1 -3
- data/lib/her/middleware/json_api_parser.rb +1 -1
- data/lib/her/model/associations/association.rb +31 -0
- data/lib/her/model/associations/association_proxy.rb +1 -1
- data/lib/her/model/attributes.rb +2 -0
- data/lib/her/model/orm.rb +79 -6
- data/lib/her/model/parse.rb +8 -12
- data/lib/her/model/relation.rb +45 -1
- data/lib/her/version.rb +1 -1
- data/spec/api_spec.rb +34 -31
- data/spec/collection_spec.rb +25 -10
- data/spec/json_api/model_spec.rb +75 -72
- data/spec/middleware/accept_json_spec.rb +1 -1
- data/spec/middleware/first_level_parse_json_spec.rb +20 -20
- data/spec/middleware/json_api_parser_spec.rb +26 -7
- data/spec/middleware/second_level_parse_json_spec.rb +8 -9
- data/spec/model/associations/association_proxy_spec.rb +2 -5
- data/spec/model/associations_spec.rb +248 -161
- data/spec/model/attributes_spec.rb +106 -99
- data/spec/model/callbacks_spec.rb +58 -26
- data/spec/model/dirty_spec.rb +30 -29
- data/spec/model/http_spec.rb +67 -35
- data/spec/model/introspection_spec.rb +26 -22
- data/spec/model/nested_attributes_spec.rb +31 -31
- data/spec/model/orm_spec.rb +312 -155
- data/spec/model/parse_spec.rb +77 -77
- data/spec/model/paths_spec.rb +109 -109
- data/spec/model/relation_spec.rb +76 -68
- data/spec/model/validations_spec.rb +6 -6
- data/spec/model_spec.rb +17 -17
- data/spec/spec_helper.rb +2 -3
- data/spec/support/macros/model_macros.rb +2 -2
- metadata +32 -59
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# Maintenance Update 29th Sept 2016
|
2
|
+
|
2
3
|
Hi folks, [@edtjones](https://github.com/edtjones) here. Rémi has handed me the keys to Her and [@foxpaul](https://github.com/foxpaul) and I will be trying to do the library justice with the help of the community. There's loads to do; we'll get in touch with everyone who's raised a PR as soon as possible and figure out a plan of action.
|
3
4
|
|
4
5
|
# Rails 5 support
|
@@ -15,6 +16,7 @@ If you need Rails 5 support, version 0.8.2 is for you!
|
|
15
16
|
<a href="https://codeclimate.com/github/remiprev/her"><img src="http://img.shields.io/codeclimate/github/remiprev/her.svg" /></a>
|
16
17
|
<a href='https://gemnasium.com/remiprev/her'><img src="http://img.shields.io/gemnasium/remiprev/her.svg" /></a>
|
17
18
|
<a href="https://travis-ci.org/remiprev/her"><img src="http://img.shields.io/travis/remiprev/her/master.svg" /></a>
|
19
|
+
<a href="https://gitter.im/her-orm/Lobby"><img src="https://badges.gitter.im/her-orm/Lobby.png" alt="Gitter chat" title="" data-pin-nopin="true"></a>
|
18
20
|
</p>
|
19
21
|
|
20
22
|
---
|
@@ -173,6 +175,24 @@ end
|
|
173
175
|
|
174
176
|
Now, each HTTP request made by Her will have the `X-API-Token` header.
|
175
177
|
|
178
|
+
### Basic Http Authentication
|
179
|
+
Her can use basic http auth by adding a line to your initializer
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
# config/initializers/her.rb
|
183
|
+
Her::API.setup url: "https://api.example.com" do |c|
|
184
|
+
# Request
|
185
|
+
c.use Faraday::Request::BasicAuthentication, 'myusername', 'mypassword'
|
186
|
+
c.use Faraday::Request::UrlEncoded
|
187
|
+
|
188
|
+
# Response
|
189
|
+
c.use Her::Middleware::DefaultParseJSON
|
190
|
+
|
191
|
+
# Adapter
|
192
|
+
c.use Faraday::Adapter::NetHttp
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
176
196
|
### OAuth
|
177
197
|
|
178
198
|
Using the `faraday_middleware` and `simple_oauth` gems, it’s fairly easy to use OAuth authentication with Her.
|
@@ -397,8 +417,8 @@ You can use the association methods to build new objects and save them.
|
|
397
417
|
@user.comments.build(body: "Just a draft")
|
398
418
|
# => [#<Comment body="Just a draft" user_id=1>]
|
399
419
|
|
400
|
-
@user.comments.create(body: "Hello world.")
|
401
|
-
# POST "/
|
420
|
+
@user.comments.create(body: "Hello world.", user_id: 1)
|
421
|
+
# POST "/comments" with `body=Hello+world.&user_id=1`
|
402
422
|
# => [#<Comment id=3 body="Hello world." user_id=1>]
|
403
423
|
```
|
404
424
|
|
@@ -593,7 +613,7 @@ users = Users.all
|
|
593
613
|
#### JSON API support
|
594
614
|
|
595
615
|
To consume a JSON API 1.0 compliant service, it must return data in accordance with the [JSON API spec](http://jsonapi.org/). The general format
|
596
|
-
of the data is as follows:
|
616
|
+
of the data is as follows:
|
597
617
|
|
598
618
|
```json
|
599
619
|
{ "data": {
|
data/her.gemspec
CHANGED
@@ -18,9 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
20
|
s.add_development_dependency "rake", "~> 10.0"
|
21
|
-
s.add_development_dependency "rspec", "~>
|
22
|
-
s.add_development_dependency "rspec-its", "~> 1.0"
|
23
|
-
s.add_development_dependency "fivemat", "~> 1.2"
|
21
|
+
s.add_development_dependency "rspec", "~> 3.5"
|
24
22
|
s.add_development_dependency "json", "~> 1.8"
|
25
23
|
|
26
24
|
s.add_runtime_dependency "activemodel", ">= 3.0.0", "<= 6.0.0"
|
@@ -49,6 +49,7 @@ module Her
|
|
49
49
|
|
50
50
|
return @cached_result unless @params.any? || @cached_result.nil?
|
51
51
|
return @parent.attributes[@name] unless @params.any? || @parent.attributes[@name].blank?
|
52
|
+
return @opts[:default].try(:dup) if @parent.new?
|
52
53
|
|
53
54
|
path = build_association_path lambda { "#{@parent.request_path(@params)}#{@opts[:path]}" }
|
54
55
|
@klass.get(path, @params).tap do |result|
|
@@ -65,6 +66,13 @@ module Her
|
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
69
|
+
# @private
|
70
|
+
def reset
|
71
|
+
@params = {}
|
72
|
+
@cached_result = nil
|
73
|
+
@parent.attributes.delete(@name)
|
74
|
+
end
|
75
|
+
|
68
76
|
# Add query parameters to the HTTP request performed to fetch the data
|
69
77
|
#
|
70
78
|
# @example
|
@@ -97,6 +105,29 @@ module Her
|
|
97
105
|
@klass.get_resource(path, @params)
|
98
106
|
end
|
99
107
|
|
108
|
+
# Refetches the association and puts the proxy back in its initial state,
|
109
|
+
# which is unloaded. Cached associations are cleared.
|
110
|
+
#
|
111
|
+
# @example
|
112
|
+
# class User
|
113
|
+
# include Her::Model
|
114
|
+
# has_many :comments
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# class Comment
|
118
|
+
# include Her::Model
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# user = User.find(1)
|
122
|
+
# user.comments = [#<Comment(comments/2) id=2 body="Hello!">]
|
123
|
+
# user.comments.first.id = "Oops"
|
124
|
+
# user.comments.reload # => [#<Comment(comments/2) id=2 body="Hello!">]
|
125
|
+
# # Fetched again via GET "/users/1/comments"
|
126
|
+
def reload
|
127
|
+
reset
|
128
|
+
fetch
|
129
|
+
end
|
130
|
+
|
100
131
|
end
|
101
132
|
end
|
102
133
|
end
|
data/lib/her/model/attributes.rb
CHANGED
data/lib/her/model/orm.rb
CHANGED
@@ -39,8 +39,8 @@ module Her
|
|
39
39
|
callback = new? ? :create : :update
|
40
40
|
method = self.class.method_for(callback)
|
41
41
|
|
42
|
-
run_callbacks
|
43
|
-
run_callbacks
|
42
|
+
run_callbacks :save do
|
43
|
+
run_callbacks callback do
|
44
44
|
params = to_params
|
45
45
|
self.class.request(to_params.merge(:_method => method, :_path => request_path)) do |parsed_data, response|
|
46
46
|
assign_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
|
@@ -49,7 +49,7 @@ module Her
|
|
49
49
|
|
50
50
|
return false if !response.success? || @response_errors.any?
|
51
51
|
if self.changed_attributes.present?
|
52
|
-
@previously_changed = self.
|
52
|
+
@previously_changed = self.changes.clone
|
53
53
|
self.changed_attributes.clear
|
54
54
|
end
|
55
55
|
end
|
@@ -80,12 +80,81 @@ module Her
|
|
80
80
|
assign_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
|
81
81
|
@metadata = parsed_data[:metadata]
|
82
82
|
@response_errors = parsed_data[:errors]
|
83
|
-
@destroyed =
|
83
|
+
@destroyed = response.success?
|
84
84
|
end
|
85
85
|
end
|
86
86
|
self
|
87
87
|
end
|
88
88
|
|
89
|
+
# Refetches the resource
|
90
|
+
#
|
91
|
+
# This method finds the resource by its primary key (which could be
|
92
|
+
# assigned manually) and modifies the object in-place.
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# user = User.find(1)
|
96
|
+
# # => #<User(users/1) id=1 name="Tobias Fünke">
|
97
|
+
# user.name = "Oops"
|
98
|
+
# user.reload # Fetched again via GET "/users/1"
|
99
|
+
# # => #<User(users/1) id=1 name="Tobias Fünke">
|
100
|
+
def reload(options = nil)
|
101
|
+
fresh_object = self.class.find(id)
|
102
|
+
assign_attributes(fresh_object.attributes)
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
|
107
|
+
# if the predicate returns +true+ the attribute will become +false+. This
|
108
|
+
# method toggles directly the underlying value without calling any setter.
|
109
|
+
# Returns +self+.
|
110
|
+
#
|
111
|
+
# @example
|
112
|
+
# user = User.first
|
113
|
+
# user.admin? # => false
|
114
|
+
# user.toggle(:admin)
|
115
|
+
# user.admin? # => true
|
116
|
+
def toggle(attribute)
|
117
|
+
attributes[attribute] = !public_send("#{attribute}?")
|
118
|
+
self
|
119
|
+
end
|
120
|
+
|
121
|
+
# Wrapper around #toggle that saves the resource. Saving is subjected to
|
122
|
+
# validation checks. Returns +true+ if the record could be saved.
|
123
|
+
def toggle!(attribute)
|
124
|
+
toggle(attribute) && save
|
125
|
+
end
|
126
|
+
|
127
|
+
# Initializes +attribute+ to zero if +nil+ and adds the value passed as
|
128
|
+
# +by+ (default is 1). The increment is performed directly on the
|
129
|
+
# underlying attribute, no setter is invoked. Only makes sense for
|
130
|
+
# number-based attributes. Returns +self+.
|
131
|
+
def increment(attribute, by = 1)
|
132
|
+
attributes[attribute] ||= 0
|
133
|
+
attributes[attribute] += by
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
# Wrapper around #increment that saves the resource. Saving is subjected
|
138
|
+
# to validation checks. Returns +self+.
|
139
|
+
def increment!(attribute, by = 1)
|
140
|
+
increment(attribute, by) && save
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
# Initializes +attribute+ to zero if +nil+ and substracts the value passed as
|
145
|
+
# +by+ (default is 1). The decrement is performed directly on the
|
146
|
+
# underlying attribute, no setter is invoked. Only makes sense for
|
147
|
+
# number-based attributes. Returns +self+.
|
148
|
+
def decrement(attribute, by = 1)
|
149
|
+
increment(attribute, -by)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Wrapper around #decrement that saves the resource. Saving is subjected
|
153
|
+
# to validation checks. Returns +self+.
|
154
|
+
def decrement!(attribute, by = 1)
|
155
|
+
increment!(attribute, -by)
|
156
|
+
end
|
157
|
+
|
89
158
|
module ClassMethods
|
90
159
|
# Create a new chainable scope
|
91
160
|
#
|
@@ -134,7 +203,8 @@ module Her
|
|
134
203
|
end
|
135
204
|
|
136
205
|
# Delegate the following methods to `scoped`
|
137
|
-
[:all, :where, :create, :build, :find, :
|
206
|
+
[:all, :where, :create, :build, :find, :find_by, :find_or_create_by,
|
207
|
+
:find_or_initialize_by, :first_or_create, :first_or_initialize].each do |method|
|
138
208
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
139
209
|
def #{method}(*params)
|
140
210
|
scoped.send(#{method.to_sym.inspect}, *params)
|
@@ -160,7 +230,10 @@ module Her
|
|
160
230
|
# # Called via DELETE "/users/1"
|
161
231
|
def destroy_existing(id, params={})
|
162
232
|
request(params.merge(:_method => method_for(:destroy), :_path => build_request_path(params.merge(primary_key => id)))) do |parsed_data, response|
|
163
|
-
|
233
|
+
data = parse(parsed_data[:data])
|
234
|
+
metadata = parsed_data[:metadata]
|
235
|
+
response_errors = parsed_data[:errors]
|
236
|
+
new(data.merge(:_destroyed => response.success?, :metadata => metadata, :response_errors => response_errors))
|
164
237
|
end
|
165
238
|
end
|
166
239
|
|
data/lib/her/model/parse.rb
CHANGED
@@ -52,20 +52,16 @@ module Her
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
|
56
55
|
# @private
|
57
|
-
# TODO: Handle has_one
|
58
56
|
def embeded_params(attributes)
|
59
|
-
associations
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
hash
|
57
|
+
associations.values.flatten.each_with_object({}) do |definition, hash|
|
58
|
+
value = case association = attributes[definition[:name]]
|
59
|
+
when Her::Collection, Array
|
60
|
+
association.map { |a| a.to_params }.reject(&:empty?)
|
61
|
+
when Her::Model
|
62
|
+
association.to_params
|
63
|
+
end
|
64
|
+
hash[definition[:data_key]] = value if value.present?
|
69
65
|
end
|
70
66
|
end
|
71
67
|
|
data/lib/her/model/relation.rb
CHANGED
@@ -65,7 +65,7 @@ module Her
|
|
65
65
|
# @private
|
66
66
|
def fetch
|
67
67
|
@_fetch ||= begin
|
68
|
-
path = @parent.build_request_path(@params)
|
68
|
+
path = @parent.build_request_path(@parent.collection_path, @params)
|
69
69
|
method = @parent.method_for(:find)
|
70
70
|
@parent.request(@params.merge(:_method => method, :_path => path)) do |parsed_data, response|
|
71
71
|
@parent.new_collection(parsed_data)
|
@@ -109,6 +109,50 @@ module Her
|
|
109
109
|
ids.length > 1 || ids.first.kind_of?(Array) ? results : results.first
|
110
110
|
end
|
111
111
|
|
112
|
+
# Fetch first resource with the given attributes.
|
113
|
+
#
|
114
|
+
# If no resource is found, returns <tt>nil</tt>.
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# @user = User.find_by(name: "Tobias", age: 42)
|
118
|
+
# # Called via GET "/users?name=Tobias&age=42"
|
119
|
+
def find_by(params)
|
120
|
+
where(params).first
|
121
|
+
end
|
122
|
+
|
123
|
+
# Fetch first resource with the given attributes, or create a resource
|
124
|
+
# with the attributes if one is not found.
|
125
|
+
#
|
126
|
+
# @example
|
127
|
+
# @user = User.find_or_create_by(email: "remi@example.com")
|
128
|
+
#
|
129
|
+
# # Returns the first item in the collection if present:
|
130
|
+
# # Called via GET "/users?email=remi@example.com"
|
131
|
+
#
|
132
|
+
# # If collection is empty:
|
133
|
+
# # POST /users with `email=remi@example.com`
|
134
|
+
# @user.email # => "remi@example.com"
|
135
|
+
# @user.new? # => false
|
136
|
+
def find_or_create_by(attributes)
|
137
|
+
find_by(attributes) || create(attributes)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Fetch first resource with the given attributes, or initialize a resource
|
141
|
+
# with the attributes if one is not found.
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
# @user = User.find_or_initialize_by(email: "remi@example.com")
|
145
|
+
#
|
146
|
+
# # Returns the first item in the collection if present:
|
147
|
+
# # Called via GET "/users?email=remi@example.com"
|
148
|
+
#
|
149
|
+
# # If collection is empty:
|
150
|
+
# @user.email # => "remi@example.com"
|
151
|
+
# @user.new? # => true
|
152
|
+
def find_or_initialize_by(attributes)
|
153
|
+
find_by(attributes) || build(attributes)
|
154
|
+
end
|
155
|
+
|
112
156
|
# Create a resource and return it
|
113
157
|
#
|
114
158
|
# @example
|
data/lib/her/version.rb
CHANGED
data/spec/api_spec.rb
CHANGED
@@ -8,21 +8,24 @@ describe Her::API do
|
|
8
8
|
describe "#setup" do
|
9
9
|
context "when setting custom middleware" do
|
10
10
|
before do
|
11
|
-
class Foo; end
|
12
|
-
class Bar; end
|
11
|
+
class Foo; end
|
12
|
+
class Bar; end
|
13
13
|
|
14
|
-
subject.setup :
|
14
|
+
subject.setup url: "https://api.example.com" do |connection|
|
15
15
|
connection.use Foo
|
16
16
|
connection.use Bar
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
specify { subject.connection.builder.handlers.
|
20
|
+
specify { expect(subject.connection.builder.handlers).to eq([Foo, Bar]) }
|
21
21
|
end
|
22
22
|
|
23
23
|
context "when setting custom options" do
|
24
|
-
before { subject.setup :
|
25
|
-
|
24
|
+
before { subject.setup foo: { bar: "baz" }, url: "https://api.example.com" }
|
25
|
+
|
26
|
+
describe "#options" do
|
27
|
+
it { expect(subject.options).to eq(foo: { bar: "baz" }, url: "https://api.example.com") }
|
28
|
+
end
|
26
29
|
end
|
27
30
|
end
|
28
31
|
|
@@ -30,83 +33,83 @@ describe Her::API do
|
|
30
33
|
before do
|
31
34
|
class SimpleParser < Faraday::Response::Middleware
|
32
35
|
def on_complete(env)
|
33
|
-
env[:body] = { :
|
36
|
+
env[:body] = { data: env[:body] }
|
34
37
|
end
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
38
41
|
context "making HTTP requests" do
|
39
|
-
let(:parsed_data) { subject.request(:
|
42
|
+
let(:parsed_data) { subject.request(_method: :get, _path: "/foo")[:parsed_data] }
|
40
43
|
before do
|
41
|
-
subject.setup :
|
44
|
+
subject.setup url: "https://api.example.com" do |builder|
|
42
45
|
builder.use SimpleParser
|
43
|
-
builder.adapter(:test) { |stub| stub.get("/foo") {
|
46
|
+
builder.adapter(:test) { |stub| stub.get("/foo") { [200, {}, "Foo, it is."] } }
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
47
|
-
specify { parsed_data[:data].
|
50
|
+
specify { expect(parsed_data[:data]).to eq("Foo, it is.") }
|
48
51
|
end
|
49
52
|
|
50
53
|
context "making HTTP requests while specifying custom HTTP headers" do
|
51
|
-
let(:parsed_data) { subject.request(:
|
54
|
+
let(:parsed_data) { subject.request(_method: :get, _path: "/foo", _headers: { "X-Page" => 2 })[:parsed_data] }
|
52
55
|
|
53
56
|
before do
|
54
|
-
subject.setup :
|
57
|
+
subject.setup url: "https://api.example.com" do |builder|
|
55
58
|
builder.use SimpleParser
|
56
|
-
builder.adapter(:test) { |stub| stub.get("/foo") { |env| [200, {}, "Foo, it is page #{env[:request_headers][
|
59
|
+
builder.adapter(:test) { |stub| stub.get("/foo") { |env| [200, {}, "Foo, it is page #{env[:request_headers]['X-Page']}."] } }
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
60
|
-
specify { parsed_data[:data].
|
63
|
+
specify { expect(parsed_data[:data]).to eq("Foo, it is page 2.") }
|
61
64
|
end
|
62
65
|
|
63
66
|
context "parsing a request with the default parser" do
|
64
|
-
let(:parsed_data) { subject.request(:
|
67
|
+
let(:parsed_data) { subject.request(_method: :get, _path: "users/1")[:parsed_data] }
|
65
68
|
before do
|
66
|
-
subject.setup :
|
69
|
+
subject.setup url: "https://api.example.com" do |builder|
|
67
70
|
builder.use Her::Middleware::FirstLevelParseJSON
|
68
71
|
builder.adapter :test do |stub|
|
69
|
-
stub.get("/users/1") {
|
72
|
+
stub.get("/users/1") { [200, {}, MultiJson.dump(id: 1, name: "George Michael Bluth", errors: ["This is a single error"], metadata: { page: 1, per_page: 10 })] }
|
70
73
|
end
|
71
74
|
end
|
72
75
|
end
|
73
76
|
|
74
77
|
specify do
|
75
|
-
parsed_data[:data].
|
76
|
-
parsed_data[:errors].
|
77
|
-
parsed_data[:metadata].
|
78
|
+
expect(parsed_data[:data]).to eq(id: 1, name: "George Michael Bluth")
|
79
|
+
expect(parsed_data[:errors]).to eq(["This is a single error"])
|
80
|
+
expect(parsed_data[:metadata]).to eq(page: 1, per_page: 10)
|
78
81
|
end
|
79
82
|
end
|
80
83
|
|
81
84
|
context "parsing a request with a custom parser" do
|
82
|
-
let(:parsed_data) { subject.request(:
|
85
|
+
let(:parsed_data) { subject.request(_method: :get, _path: "users/1")[:parsed_data] }
|
83
86
|
before do
|
84
87
|
class CustomParser < Faraday::Response::Middleware
|
85
88
|
def on_complete(env)
|
86
|
-
json = MultiJson.load(env[:body], :
|
89
|
+
json = MultiJson.load(env[:body], symbolize_keys: true)
|
87
90
|
errors = json.delete(:errors) || []
|
88
91
|
metadata = json.delete(:metadata) || {}
|
89
92
|
env[:body] = {
|
90
|
-
:
|
91
|
-
:
|
92
|
-
:
|
93
|
+
data: json,
|
94
|
+
errors: errors,
|
95
|
+
metadata: metadata
|
93
96
|
}
|
94
97
|
end
|
95
98
|
end
|
96
99
|
|
97
|
-
subject.setup :
|
100
|
+
subject.setup url: "https://api.example.com" do |builder|
|
98
101
|
builder.use CustomParser
|
99
102
|
builder.use Faraday::Request::UrlEncoded
|
100
103
|
builder.adapter :test do |stub|
|
101
|
-
stub.get("/users/1") {
|
104
|
+
stub.get("/users/1") { [200, {}, MultiJson.dump(id: 1, name: "George Michael Bluth")] }
|
102
105
|
end
|
103
106
|
end
|
104
107
|
end
|
105
108
|
|
106
109
|
specify do
|
107
|
-
parsed_data[:data].
|
108
|
-
parsed_data[:errors].
|
109
|
-
parsed_data[:metadata].
|
110
|
+
expect(parsed_data[:data]).to eq(id: 1, name: "George Michael Bluth")
|
111
|
+
expect(parsed_data[:errors]).to eq([])
|
112
|
+
expect(parsed_data[:metadata]).to eq({})
|
110
113
|
end
|
111
114
|
end
|
112
115
|
end
|
data/spec/collection_spec.rb
CHANGED
@@ -1,26 +1,41 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe Her::Collection do
|
4
|
-
|
5
4
|
let(:items) { [1, 2, 3, 4] }
|
6
|
-
let(:metadata) { { :
|
7
|
-
let(:errors) { { :
|
5
|
+
let(:metadata) { { name: "Testname" } }
|
6
|
+
let(:errors) { { name: ["not_present"] } }
|
8
7
|
|
9
8
|
describe "#new" do
|
10
9
|
context "without parameters" do
|
11
10
|
subject { Her::Collection.new }
|
12
11
|
|
13
|
-
it {
|
14
|
-
|
15
|
-
|
12
|
+
it { is_expected.to eq([]) }
|
13
|
+
|
14
|
+
describe "#metadata" do
|
15
|
+
subject { super().metadata }
|
16
|
+
it { is_expected.to eq({}) }
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#errors" do
|
20
|
+
subject { super().errors }
|
21
|
+
it { is_expected.to eq({}) }
|
22
|
+
end
|
16
23
|
end
|
17
24
|
|
18
25
|
context "with parameters" do
|
19
26
|
subject { Her::Collection.new(items, metadata, errors) }
|
20
27
|
|
21
|
-
it {
|
22
|
-
|
23
|
-
|
28
|
+
it { is_expected.to eq([1, 2, 3, 4]) }
|
29
|
+
|
30
|
+
describe "#metadata" do
|
31
|
+
subject { super().metadata }
|
32
|
+
it { is_expected.to eq(name: "Testname") }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#errors" do
|
36
|
+
subject { super().errors }
|
37
|
+
it { is_expected.to eq(name: ["not_present"]) }
|
38
|
+
end
|
24
39
|
end
|
25
40
|
end
|
26
41
|
end
|