her 0.8.6 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/README.md +4 -2
- data/lib/her/api.rb +1 -1
- 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 +74 -4
- 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/middleware/json_api_parser_spec.rb +20 -0
- data/spec/model/associations_spec.rb +49 -3
- data/spec/model/attributes_spec.rb +8 -0
- data/spec/model/dirty_spec.rb +1 -1
- data/spec/model/orm_spec.rb +134 -4
- data/spec/model/relation_spec.rb +8 -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: 458601a1de64f520013856ef805b17e8293bfb9d
|
4
|
+
data.tar.gz: 1e965c550664039d6b6f649a9b10a7821d90f717
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7d147d58050f847942df92440c9e71013c1fc54fbb56d0a6e5dd9d6167a1ffc4e30c329524f1bac07a378d06991198cb1bf44215067ab911f2d0ff1ee5b95f7
|
7
|
+
data.tar.gz: 1c3ab969c7fbe8f90f730945cd9392f9e2d3df6b3df577291f86f97aa7405758f1086a7708e2586760d674a362703c465bd66516f828d1c1a389fb7c304f4a16
|
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
|
---
|
@@ -182,7 +184,7 @@ Her::API.setup url: "https://api.example.com" do |c|
|
|
182
184
|
# Request
|
183
185
|
c.use Faraday::Request::BasicAuthentication, 'myusername', 'mypassword'
|
184
186
|
c.use Faraday::Request::UrlEncoded
|
185
|
-
|
187
|
+
|
186
188
|
# Response
|
187
189
|
c.use Her::Middleware::DefaultParseJSON
|
188
190
|
|
@@ -611,7 +613,7 @@ users = Users.all
|
|
611
613
|
#### JSON API support
|
612
614
|
|
613
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
|
614
|
-
of the data is as follows:
|
616
|
+
of the data is as follows:
|
615
617
|
|
616
618
|
```json
|
617
619
|
{ "data": {
|
data/lib/her/api.rb
CHANGED
@@ -6,7 +6,7 @@ module Her
|
|
6
6
|
attr_reader :connection, :options
|
7
7
|
|
8
8
|
# Constants
|
9
|
-
FARADAY_OPTIONS = [:request, :proxy, :ssl, :builder, :url, :parallel_manager, :params, :headers, :builder_class
|
9
|
+
FARADAY_OPTIONS = [:request, :proxy, :ssl, :builder, :url, :parallel_manager, :params, :headers, :builder_class].freeze
|
10
10
|
|
11
11
|
# Setup a default API connection. Accepted arguments and options are the same as {API#setup}.
|
12
12
|
def self.setup(opts={}, &block)
|
@@ -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
|
@@ -86,6 +86,75 @@ module Her
|
|
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)
|
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
@@ -22,6 +22,26 @@ describe Her::Middleware::JsonApiParser do
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
context "with status code 204" do
|
26
|
+
it "returns an empty body" do
|
27
|
+
env = { status: 204 }
|
28
|
+
subject.on_complete(env)
|
29
|
+
env[:body].tap do |json|
|
30
|
+
expect(json[:data]).to eq({})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with status code 304' do
|
36
|
+
it 'returns an empty body' do
|
37
|
+
env = { :status => 304 }
|
38
|
+
subject.on_complete(env)
|
39
|
+
env[:body].tap do |json|
|
40
|
+
expect(json[:data]).to eq({})
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
25
45
|
# context "with invalid JSON body" do
|
26
46
|
# let(:body) { '"foo"' }
|
27
47
|
# it 'ensures that invalid JSON throws an exception' do
|
@@ -153,14 +153,16 @@ describe Her::Model::Associations do
|
|
153
153
|
|
154
154
|
spawn_model "Foo::User" do
|
155
155
|
has_many :comments, class_name: "Foo::Comment"
|
156
|
-
has_one :role
|
157
|
-
belongs_to :organization
|
156
|
+
has_one :role, class_name: "Foo::Role"
|
157
|
+
belongs_to :organization, class_name: "Foo::Organization"
|
158
158
|
has_many :posts, inverse_of: :admin
|
159
159
|
end
|
160
|
+
|
160
161
|
spawn_model "Foo::Comment" do
|
161
162
|
belongs_to :user
|
162
163
|
parse_root_in_json true
|
163
164
|
end
|
165
|
+
|
164
166
|
spawn_model "Foo::Post" do
|
165
167
|
belongs_to :admin, class_name: "Foo::User"
|
166
168
|
end
|
@@ -180,6 +182,7 @@ describe Her::Model::Associations do
|
|
180
182
|
let(:user_with_included_data_after_save_existing) { Foo::User.save_existing(5, name: "Clancy Brown") }
|
181
183
|
let(:user_with_included_data_after_destroy) { Foo::User.new(id: 5).destroy }
|
182
184
|
let(:comment_without_included_parent_data) { Foo::Comment.new(id: 7, user_id: 1) }
|
185
|
+
let(:new_user) { Foo::User.new }
|
183
186
|
|
184
187
|
it "maps an array of included data through has_many" do
|
185
188
|
expect(@user_with_included_data.comments.first).to be_a(Foo::Comment)
|
@@ -204,6 +207,12 @@ describe Her::Model::Associations do
|
|
204
207
|
expect(@user_with_included_data.posts.first.admin.object_id).to eq(@user_with_included_data.object_id)
|
205
208
|
end
|
206
209
|
|
210
|
+
it "doesn't attempt to fetch association data for a new resource" do
|
211
|
+
expect(new_user.comments).to eq([])
|
212
|
+
expect(new_user.role).to be_nil
|
213
|
+
expect(new_user.organization).to be_nil
|
214
|
+
end
|
215
|
+
|
207
216
|
it "fetches data that was not included through has_many" do
|
208
217
|
expect(@user_without_included_data.comments.first).to be_a(Foo::Comment)
|
209
218
|
expect(@user_without_included_data.comments.length).to eq(2)
|
@@ -223,6 +232,10 @@ describe Her::Model::Associations do
|
|
223
232
|
expect(@user_without_included_data.comments.first.object_id).not_to eq(@user_without_included_data.comments.where(foo_id: 1).first.object_id)
|
224
233
|
end
|
225
234
|
|
235
|
+
it "fetches data again after being reloaded" do
|
236
|
+
expect { @user_without_included_data.comments.reload }.to change { @user_without_included_data.comments.first.object_id }
|
237
|
+
end
|
238
|
+
|
226
239
|
it "maps an array of included data through has_one" do
|
227
240
|
expect(@user_with_included_data.role).to be_a(Foo::Role)
|
228
241
|
expect(@user_with_included_data.role.object_id).to eq(@user_with_included_data.role.object_id)
|
@@ -292,6 +305,18 @@ describe Her::Model::Associations do
|
|
292
305
|
expect(params[:comments].length).to eq(2)
|
293
306
|
end
|
294
307
|
|
308
|
+
it "includes has_one relationship in params by default" do
|
309
|
+
params = @user_with_included_data.to_params
|
310
|
+
expect(params[:role]).to be_kind_of(Hash)
|
311
|
+
expect(params[:role]).not_to be_empty
|
312
|
+
end
|
313
|
+
|
314
|
+
it "includes belongs_to relationship in params by default" do
|
315
|
+
params = @user_with_included_data.to_params
|
316
|
+
expect(params[:organization]).to be_kind_of(Hash)
|
317
|
+
expect(params[:organization]).not_to be_empty
|
318
|
+
end
|
319
|
+
|
295
320
|
[:create, :save_existing, :destroy].each do |type|
|
296
321
|
context "after #{type}" do
|
297
322
|
let(:subject) { send("user_with_included_data_after_#{type}") }
|
@@ -326,15 +351,24 @@ describe Her::Model::Associations do
|
|
326
351
|
stub.get("/organizations/1") { [200, {}, { organization: { id: 1, name: "Bluth Company Foo" } }.to_json] }
|
327
352
|
end
|
328
353
|
end
|
354
|
+
|
329
355
|
spawn_model "Foo::User" do
|
330
356
|
parse_root_in_json true, format: :active_model_serializers
|
331
357
|
has_many :comments, class_name: "Foo::Comment"
|
332
|
-
|
358
|
+
has_one :role, class_name: "Foo::Role"
|
359
|
+
belongs_to :organization, class_name: "Foo::Organization"
|
333
360
|
end
|
361
|
+
|
362
|
+
spawn_model "Foo::Role" do
|
363
|
+
belongs_to :user
|
364
|
+
parse_root_in_json true, format: :active_model_serializers
|
365
|
+
end
|
366
|
+
|
334
367
|
spawn_model "Foo::Comment" do
|
335
368
|
belongs_to :user
|
336
369
|
parse_root_in_json true, format: :active_model_serializers
|
337
370
|
end
|
371
|
+
|
338
372
|
spawn_model "Foo::Organization" do
|
339
373
|
parse_root_in_json true, format: :active_model_serializers
|
340
374
|
end
|
@@ -392,6 +426,18 @@ describe Her::Model::Associations do
|
|
392
426
|
expect(params[:comments]).to be_kind_of(Array)
|
393
427
|
expect(params[:comments].length).to eq(2)
|
394
428
|
end
|
429
|
+
|
430
|
+
it "includes has_one relationships in params by default" do
|
431
|
+
params = @user_with_included_data.to_params
|
432
|
+
expect(params[:role]).to be_kind_of(Hash)
|
433
|
+
expect(params[:role]).not_to be_empty
|
434
|
+
end
|
435
|
+
|
436
|
+
it "includes belongs_to relationship in params by default" do
|
437
|
+
params = @user_with_included_data.to_params
|
438
|
+
expect(params[:organization]).to be_kind_of(Hash)
|
439
|
+
expect(params[:organization]).not_to be_empty
|
440
|
+
end
|
395
441
|
end
|
396
442
|
|
397
443
|
context "handling associations with details" do
|
@@ -11,6 +11,14 @@ describe Her::Model::Attributes do
|
|
11
11
|
expect(@new_user.fullname).to eq("Tobias Fünke")
|
12
12
|
end
|
13
13
|
|
14
|
+
it "handles new resource with block" do
|
15
|
+
@new_user = Foo::User.new do |user|
|
16
|
+
user.fullname = "Tobias Fünke"
|
17
|
+
end
|
18
|
+
expect(@new_user.new?).to be_truthy
|
19
|
+
expect(@new_user.fullname).to eq("Tobias Fünke")
|
20
|
+
end
|
21
|
+
|
14
22
|
it "accepts new resource with strings as hash keys" do
|
15
23
|
@new_user = Foo::User.new("fullname" => "Tobias Fünke")
|
16
24
|
expect(@new_user.fullname).to eq("Tobias Fünke")
|
data/spec/model/dirty_spec.rb
CHANGED
@@ -49,7 +49,7 @@ describe "Her::Model and ActiveModel::Dirty" do
|
|
49
49
|
it "tracks previous changes" do
|
50
50
|
user.fullname = "Tobias Fünke"
|
51
51
|
user.save
|
52
|
-
expect(user.previous_changes).to eq("fullname" => "Lindsay Fünke")
|
52
|
+
expect(user.previous_changes).to eq("fullname" => ["Lindsay Fünke", "Tobias Fünke"])
|
53
53
|
end
|
54
54
|
|
55
55
|
it "tracks dirty attribute for mass assign for dynamic created attributes" do
|
data/spec/model/orm_spec.rb
CHANGED
@@ -107,12 +107,12 @@ describe Her::Model::ORM do
|
|
107
107
|
|
108
108
|
it "handles metadata on a destroyed resource" do
|
109
109
|
@user = User.destroy_existing(1)
|
110
|
-
@user.metadata[:foo].
|
110
|
+
expect(@user.metadata[:foo]).to eq("bar")
|
111
111
|
end
|
112
112
|
|
113
113
|
it "handles error data on a destroyed resource" do
|
114
114
|
@user = User.destroy_existing(1)
|
115
|
-
@user.response_errors.
|
115
|
+
expect(@user.response_errors).to eq(%w(Yes Sir))
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
@@ -212,6 +212,8 @@ describe Her::Model::ORM do
|
|
212
212
|
stub.get("/users?age=42&foo=bar") { [200, {}, [{ id: 3, age: 42 }].to_json] }
|
213
213
|
stub.get("/users?age=42") { [200, {}, [{ id: 1, age: 42 }].to_json] }
|
214
214
|
stub.get("/users?age=40") { [200, {}, [{ id: 1, age: 40 }].to_json] }
|
215
|
+
stub.get("/users?name=baz") { [200, {}, [].to_json] }
|
216
|
+
stub.post("/users") { [200, {}, { id: 5, name: "baz" }.to_json] }
|
215
217
|
end
|
216
218
|
end
|
217
219
|
|
@@ -264,6 +266,24 @@ describe Her::Model::ORM do
|
|
264
266
|
expect(@users[1].id).to eq(2)
|
265
267
|
end
|
266
268
|
|
269
|
+
it "handles finding by attributes" do
|
270
|
+
@user = User.find_by(age: 42)
|
271
|
+
expect(@user).to be_a(User)
|
272
|
+
expect(@user.id).to eq(1)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "handles find or create by attributes" do
|
276
|
+
@user = User.find_or_create_by(name: "baz")
|
277
|
+
expect(@user).to be_a(User)
|
278
|
+
expect(@user.id).to eq(5)
|
279
|
+
end
|
280
|
+
|
281
|
+
it "handles find or initialize by attributes" do
|
282
|
+
@user = User.find_or_initialize_by(name: "baz")
|
283
|
+
expect(@user).to be_a(User)
|
284
|
+
expect(@user).to_not be_persisted
|
285
|
+
end
|
286
|
+
|
267
287
|
it "handles finding with other parameters" do
|
268
288
|
@users = User.where(age: 42, foo: "bar").all
|
269
289
|
expect(@users).to be_kind_of(Array)
|
@@ -275,6 +295,14 @@ describe Her::Model::ORM do
|
|
275
295
|
expect(@users.where(age: 42)).to be_all { |u| u.age == 42 }
|
276
296
|
expect(@users.where(age: 40)).to be_all { |u| u.age == 40 }
|
277
297
|
end
|
298
|
+
|
299
|
+
it "handles reloading a resource" do
|
300
|
+
@user = User.find(1)
|
301
|
+
@user.age = "Oops"
|
302
|
+
@user.reload
|
303
|
+
expect(@user.age).to eq 42
|
304
|
+
expect(@user).to be_persisted
|
305
|
+
end
|
278
306
|
end
|
279
307
|
|
280
308
|
context "building resources" do
|
@@ -371,12 +399,15 @@ describe Her::Model::ORM do
|
|
371
399
|
builder.use Her::Middleware::FirstLevelParseJSON
|
372
400
|
builder.use Faraday::Request::UrlEncoded
|
373
401
|
builder.adapter :test do |stub|
|
374
|
-
stub.get("/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke" }.to_json] }
|
375
|
-
stub.put("/users/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke" }.to_json] }
|
402
|
+
stub.get("/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke", admin: false }.to_json] }
|
403
|
+
stub.put("/users/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke", admin: true }.to_json] }
|
404
|
+
stub.get("/pages/1") { [200, {}, { id: 1, views: 1, unique_visitors: 4 }.to_json] }
|
405
|
+
stub.put("/pages/1") { [200, {}, { id: 1, views: 2, unique_visitors: 3 }.to_json] }
|
376
406
|
end
|
377
407
|
end
|
378
408
|
|
379
409
|
spawn_model "Foo::User"
|
410
|
+
spawn_model "Foo::Page"
|
380
411
|
end
|
381
412
|
|
382
413
|
it "handle resource data update without saving it" do
|
@@ -397,6 +428,58 @@ describe Her::Model::ORM do
|
|
397
428
|
@user.save
|
398
429
|
expect(@user.fullname).to eq("Lindsay Fünke")
|
399
430
|
end
|
431
|
+
|
432
|
+
it "handles resource update through #toggle without saving it" do
|
433
|
+
@user = Foo::User.find(1)
|
434
|
+
expect(@user.admin).to be_falsey
|
435
|
+
expect(@user).to_not receive(:save)
|
436
|
+
@user.toggle(:admin)
|
437
|
+
expect(@user.admin).to be_truthy
|
438
|
+
end
|
439
|
+
|
440
|
+
it "handles resource update through #toggle!" do
|
441
|
+
@user = Foo::User.find(1)
|
442
|
+
expect(@user.admin).to be_falsey
|
443
|
+
expect(@user).to receive(:save).and_return(true)
|
444
|
+
@user.toggle!(:admin)
|
445
|
+
expect(@user.admin).to be_truthy
|
446
|
+
end
|
447
|
+
|
448
|
+
it "handles resource update through #increment without saving it" do
|
449
|
+
page = Foo::Page.find(1)
|
450
|
+
expect(page.views).to be 1
|
451
|
+
expect(page).to_not receive(:save)
|
452
|
+
page.increment(:views)
|
453
|
+
expect(page.views).to be 2
|
454
|
+
page.increment(:views, 2)
|
455
|
+
expect(page.views).to be 4
|
456
|
+
end
|
457
|
+
|
458
|
+
it "handles resource update through #increment!" do
|
459
|
+
page = Foo::Page.find(1)
|
460
|
+
expect(page.views).to be 1
|
461
|
+
expect(page).to receive(:save).and_return(true)
|
462
|
+
page.increment!(:views)
|
463
|
+
expect(page.views).to be 2
|
464
|
+
end
|
465
|
+
|
466
|
+
it "handles resource update through #decrement without saving it" do
|
467
|
+
page = Foo::Page.find(1)
|
468
|
+
expect(page.unique_visitors).to be 4
|
469
|
+
expect(page).to_not receive(:save)
|
470
|
+
page.decrement(:unique_visitors)
|
471
|
+
expect(page.unique_visitors).to be 3
|
472
|
+
page.decrement(:unique_visitors, 2)
|
473
|
+
expect(page.unique_visitors).to be 1
|
474
|
+
end
|
475
|
+
|
476
|
+
it "handles resource update through #decrement!" do
|
477
|
+
page = Foo::Page.find(1)
|
478
|
+
expect(page.unique_visitors).to be 4
|
479
|
+
expect(page).to receive(:save).and_return(true)
|
480
|
+
page.decrement!(:unique_visitors)
|
481
|
+
expect(page.unique_visitors).to be 3
|
482
|
+
end
|
400
483
|
end
|
401
484
|
|
402
485
|
context "deleting resources" do
|
@@ -530,4 +613,51 @@ describe Her::Model::ORM do
|
|
530
613
|
end
|
531
614
|
end
|
532
615
|
end
|
616
|
+
|
617
|
+
context "registering callbacks" do
|
618
|
+
before do
|
619
|
+
Her::API.setup url: "https://api.example.com" do |builder|
|
620
|
+
builder.use Her::Middleware::FirstLevelParseJSON
|
621
|
+
builder.use Faraday::Request::UrlEncoded
|
622
|
+
builder.adapter :test do |stub|
|
623
|
+
stub.get("/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke" }.to_json] }
|
624
|
+
stub.put("/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke" }.to_json] }
|
625
|
+
stub.post("/users") { [200, {}, { id: 2, fullname: "Lindsay Fünke" }.to_json] }
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
spawn_model "User" do
|
630
|
+
before_save :before_save_callback
|
631
|
+
before_create :before_create_callback
|
632
|
+
before_update :before_update_callback
|
633
|
+
after_update :after_update_callback
|
634
|
+
after_create :after_create_callback
|
635
|
+
after_save :after_save_callback
|
636
|
+
def before_save_callback; end
|
637
|
+
def before_create_callback; end
|
638
|
+
def before_update_callback; end
|
639
|
+
def after_update_callback; end
|
640
|
+
def after_create_callback; end
|
641
|
+
def after_save_callback; end
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
it "runs create callbacks in the correct order" do
|
646
|
+
@user = User.new(fullname: "Tobias Fünke")
|
647
|
+
expect(@user).to receive(:before_save_callback).ordered
|
648
|
+
expect(@user).to receive(:before_create_callback).ordered
|
649
|
+
expect(@user).to receive(:after_create_callback).ordered
|
650
|
+
expect(@user).to receive(:after_save_callback).ordered
|
651
|
+
@user.save
|
652
|
+
end
|
653
|
+
|
654
|
+
it "runs update callbacks in the correct order" do
|
655
|
+
@user = User.find(1)
|
656
|
+
expect(@user).to receive(:before_save_callback).ordered
|
657
|
+
expect(@user).to receive(:before_update_callback).ordered
|
658
|
+
expect(@user).to receive(:after_update_callback).ordered
|
659
|
+
expect(@user).to receive(:after_save_callback).ordered
|
660
|
+
@user.save
|
661
|
+
end
|
662
|
+
end
|
533
663
|
end
|
data/spec/model/relation_spec.rb
CHANGED
@@ -10,6 +10,7 @@ describe Her::Model::Relation do
|
|
10
10
|
builder.adapter :test do |stub|
|
11
11
|
stub.get("/users?foo=1&bar=2") { ok! [{ id: 2, fullname: "Tobias Fünke" }] }
|
12
12
|
stub.get("/users?admin=1") { ok! [{ id: 1, fullname: "Tobias Fünke" }] }
|
13
|
+
stub.get("/users?id=3&foo=2") { ok! [{ id: 3, fullname: "Tobias Fünke" }] }
|
13
14
|
|
14
15
|
stub.get("/users") do
|
15
16
|
ok! [
|
@@ -41,6 +42,13 @@ describe Her::Model::Relation do
|
|
41
42
|
expect(@users.size).to eql 1
|
42
43
|
end
|
43
44
|
|
45
|
+
it "fetches the data by parameters including primary_key" do
|
46
|
+
expect(Foo::User).to receive(:request).once.and_call_original
|
47
|
+
@users = Foo::User.where(id: 3, foo: 2)
|
48
|
+
expect(@users).to respond_to(:length)
|
49
|
+
expect(@users.size).to eql 1
|
50
|
+
end
|
51
|
+
|
44
52
|
it "chains multiple where statements" do
|
45
53
|
@user = Foo::User.where(foo: 1).where(bar: 2).first
|
46
54
|
expect(@user.id).to eq(2)
|
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.
|
4
|
+
version: 0.9.0
|
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: 2017-
|
11
|
+
date: 2017-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|