her 0.8.6 → 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/.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
|