smooth_operator 1.8.0 → 1.8.3
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 +8 -8
- data/Gemfile +2 -0
- data/README.md +246 -8
- data/lib/smooth_operator/array_with_meta_data.rb +1 -1
- data/lib/smooth_operator/finder_methods.rb +1 -1
- data/lib/smooth_operator/helpers.rb +0 -2
- data/lib/smooth_operator/model_schema.rb +8 -4
- data/lib/smooth_operator/operator.rb +4 -3
- data/lib/smooth_operator/persistence.rb +16 -18
- data/lib/smooth_operator/remote_call.rb +4 -2
- data/lib/smooth_operator/serialization.rb +23 -12
- data/lib/smooth_operator/type_converter.rb +6 -6
- data/lib/smooth_operator/version.rb +1 -1
- data/smooth_operator.gemspec +2 -1
- data/spec/smooth_operator/finder_methods_spec.rb +47 -2
- data/spec/smooth_operator/persistence_spec.rb +4 -2
- data/spec/smooth_operator/remote_call_spec.rb +98 -0
- data/spec/smooth_operator/validations_spec.rb +42 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/test_server.rb +15 -1
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
N2JjYWE0YjEwY2U0MTk1NWIwZTYxMzlkZTQxNjcwM2M3NDNlZGI4Zg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZGQxNjU4NDk4OGFhZmM5NjdmYzAwNWVjOTE3YmQzMDdjZjVhMTc4YQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NjVhMWJmNzE3YTM2YzI0OTQ1YjIyNTBjYTFiODk2Y2NmZmM1YzIzY2E3M2E0
|
10
|
+
NzI1OGM4YWRhOTQyNzVkNmRjMzAwMDM3MzI0MjJlOGFlODYwODNkMjg2Y2Nm
|
11
|
+
OWNiNjE3NzZlYjc0OGRmY2Y4NTVhNGFlODI2N2JjYmNkNGRkZDI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NjM1ZDQ5NTI5YzI3MmE2Mjk2ZDQ3NmY0OGQ2OGU3ZjkzMWE5ZTVmZDNiMzQ3
|
14
|
+
ZjA5YWIyYWNhZWZhNTY4NDhiOTc4MWUzY2ZlYWYyMDQzM2M0MWU4ZjE0M2Vl
|
15
|
+
OWVhOTMzYzAzNTI4YmQzNTkzMDEwYjA5MGU1MTY3ODM5NTI1ZmM=
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -7,8 +7,9 @@ Depends only on Faraday gem, no need for ActiveSupport or any other Active* gem.
|
|
7
7
|
|
8
8
|
Although if I18n is present it will respond to .human_attribute_name method and if ActiveModel is present it will make use of 'ActiveModel::Name' to improve .model_name method.
|
9
9
|
|
10
|
+
---
|
10
11
|
|
11
|
-
## Installation
|
12
|
+
## 1) Installation
|
12
13
|
|
13
14
|
Add this line to your application's Gemfile:
|
14
15
|
|
@@ -22,15 +23,252 @@ Or install it yourself as:
|
|
22
23
|
|
23
24
|
$ gem install smooth_operator
|
24
25
|
|
26
|
+
---
|
25
27
|
|
26
|
-
## Usage
|
28
|
+
## 2) Usage and Examples
|
27
29
|
|
28
|
-
|
30
|
+
```ruby
|
31
|
+
class MyBlogResource < SmoothOperator::Base
|
32
|
+
self.endpoint = 'http://myblog.com/api/v0'
|
29
33
|
|
34
|
+
# HTTP BASIC AUTH
|
35
|
+
self.endpoint_user = 'admin'
|
36
|
+
self.endpoint_pass = 'admin'
|
37
|
+
end
|
30
38
|
|
31
|
-
|
39
|
+
class Post < MyBlogResource
|
40
|
+
end
|
41
|
+
```
|
32
42
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
43
|
+
---
|
44
|
+
|
45
|
+
### 2.1) Creating a .new 'Post' and #save it
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
post = Post.new(body: 'my first post', author: 'John Doe')
|
49
|
+
|
50
|
+
post.new_record? # true
|
51
|
+
post.persisted? # false
|
52
|
+
post.body # 'my first post'
|
53
|
+
post.author # 'John Doe'
|
54
|
+
post.something_else # will raise NoMethodError
|
55
|
+
|
56
|
+
save_result = post.save # will make a http POST call to 'http://myblog.com/api/v0/posts'
|
57
|
+
# with `{ post: { body: 'my first post', author: 'John Doe' } }`
|
58
|
+
|
59
|
+
post.last_remote_call # will contain a SmoothOperator::RemoteCall instance containing relevant information about the save remote call.
|
60
|
+
|
61
|
+
# If the server response is positive (http code between 200 and 299):
|
62
|
+
save_result # true
|
63
|
+
post.new_record? # false
|
64
|
+
post.persisted? # true
|
65
|
+
# server response contains { id: 1 } on its body
|
66
|
+
post.id # 1
|
67
|
+
|
68
|
+
# If the server response is negative (http code between 400 and 499):
|
69
|
+
save_result # false
|
70
|
+
post.new_record? # true
|
71
|
+
post.persisted? # false
|
72
|
+
# server response contains { errors: { body: ['must be less then 10 letters'] } }
|
73
|
+
post.errors.body # Array
|
74
|
+
|
75
|
+
# If the server response is an error (http code between 500 and 599), or the connection was broke:
|
76
|
+
save_result # nil
|
77
|
+
post.new_record? # true
|
78
|
+
post.persisted? # false
|
79
|
+
# server response contains { errors: { body: ['must be less then 10 letters'] } }
|
80
|
+
post.errors # will raise NoMethodError
|
81
|
+
|
82
|
+
# In the positive and negative server response comes with a json,
|
83
|
+
# e.g. { id: 1 }, post will reflect that new data
|
84
|
+
post.id # 1
|
85
|
+
|
86
|
+
# In case of error and the server response contains a json,
|
87
|
+
# e.g. { id: 1 }, post will NOT reflect that data
|
88
|
+
post.id # raise NoMethodError
|
89
|
+
|
90
|
+
```
|
91
|
+
|
92
|
+
---
|
93
|
+
|
94
|
+
### 2.2) Editing an existing record
|
95
|
+
```ruby
|
96
|
+
post = Post.find(2)
|
97
|
+
|
98
|
+
post.body = 'editing my second page'
|
99
|
+
|
100
|
+
post.save
|
101
|
+
```
|
102
|
+
|
103
|
+
---
|
104
|
+
|
105
|
+
### 2.3) Customize #save 'url', 'params' and 'options'
|
106
|
+
```ruby
|
107
|
+
post = Post.new(id: 2, body: 'editing my second page')
|
108
|
+
|
109
|
+
post.new_record? # false
|
110
|
+
post.persisted? # true
|
111
|
+
|
112
|
+
post.save("#{post.id}/save_and_add_to_list", { admin: true, post: { author: 'Agent Smith', list_id: 1 } }, { timeout: 1 })
|
113
|
+
# Will make a PUT to 'http://myblog.com/api/v0/posts/2/save_and_add_to_list'
|
114
|
+
# with { admin: true, post: { body: 'editing my second page', list_id: 1 } }
|
115
|
+
# and will only wait 1sec for the server to respond.
|
116
|
+
```
|
117
|
+
|
118
|
+
---
|
119
|
+
|
120
|
+
### 2.4) Saving using HTTP Patch verb
|
121
|
+
```ruby
|
122
|
+
class Page < MyBlogResource
|
123
|
+
self.save_http_verb = :patch
|
124
|
+
end
|
125
|
+
|
126
|
+
page = Page.find(2)
|
127
|
+
|
128
|
+
page.body = 'editing my second page'
|
129
|
+
|
130
|
+
page.save # will make a http PATCH call to 'http://myblog.com/api/v0/pages/2'
|
131
|
+
# with `{ page: { body: 'editing my second page' } }`
|
132
|
+
```
|
133
|
+
|
134
|
+
---
|
135
|
+
|
136
|
+
### 2.5) Retrieving remote objects - 'index' REST action
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
remote_call = Page.find(:all) # Will make a GET call to 'http://myblog.com/api/v0/pages'
|
140
|
+
# and will return a SmoothOperator::RemoteCall instance
|
141
|
+
|
142
|
+
pages = remote_call.objects # 'pages = remote_call.data' also works
|
143
|
+
|
144
|
+
# If the server response is positive (http code between 200 and 299):
|
145
|
+
remote_call.success? # true
|
146
|
+
remote_call.failure? # false
|
147
|
+
remote_call.error? # false
|
148
|
+
remote_call.status # true
|
149
|
+
pages = remote_call.data # array of Page instances
|
150
|
+
remote_call.http_status # server_response code
|
151
|
+
|
152
|
+
# If the server response is negative (http code between 400 and 499):
|
153
|
+
remote_call.success? # false
|
154
|
+
remote_call.failure? # true
|
155
|
+
remote_call.error? # false
|
156
|
+
remote_call.status # false
|
157
|
+
remote_call.http_status # server_response code
|
158
|
+
|
159
|
+
# If the server response is an error (http code between 500 and 599), or the connection broke:
|
160
|
+
remote_call.success? # false
|
161
|
+
remote_call.failure? # false
|
162
|
+
remote_call.error? # true
|
163
|
+
remote_call.status # nil
|
164
|
+
remote_call.http_status # server_response code or 0 if connection broke
|
165
|
+
```
|
166
|
+
|
167
|
+
---
|
168
|
+
|
169
|
+
### 2.6) Retrieving remote objects - 'show' REST action
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
remote_call = Page.find(2) # Will make a GET call to 'http://myblog.com/api/v0/pages/2'
|
173
|
+
# and will return a SmoothOperator::RemoteCall instance
|
174
|
+
|
175
|
+
page = remote_call.object # 'page = remote_call.data' also works
|
176
|
+
```
|
177
|
+
|
178
|
+
---
|
179
|
+
|
180
|
+
### 2.7) Retrieving remote objects - custom query
|
181
|
+
```ruby
|
182
|
+
remote_call = Page.find('my_pages', { q: body_contains: 'link' }, { endpoint_user: 'admin', endpoint_pass: 'new_password' })
|
183
|
+
# will make a GET call to 'http://myblog.com/api/v0/pages/my_pages?q={body_contains="link"}'
|
184
|
+
# and will change the HTTP BASIC AUTH credentials to user: 'admin' and pass: 'new_password' for this connection only.
|
185
|
+
|
186
|
+
# If the server json response is an Array [{ id: 1 }, { id: 2 }]
|
187
|
+
pages = remote.data # will return an array with 2 Page's instances
|
188
|
+
pages[0].id # 1
|
189
|
+
pages[1].id # 2
|
190
|
+
|
191
|
+
# If the server json response is a Hash { id: 3 }
|
192
|
+
page = remote.data # will return a single Page instance
|
193
|
+
page.id # 3
|
194
|
+
|
195
|
+
# If the server json response is Hash with a key called 'pages' { page: 1, total: 3, pages: [{ id: 4 }, { id: 5 }] }
|
196
|
+
pages = remote.data # will return a single ArrayWithMetaData instance, that will allow you to access to both the Page's instances array and the metadata.
|
197
|
+
pages.page # 1
|
198
|
+
pages.total # 3
|
199
|
+
|
200
|
+
pages[0].id # 4
|
201
|
+
pages[1].id # 5
|
202
|
+
```
|
203
|
+
|
204
|
+
---
|
205
|
+
|
206
|
+
## 3) Methods
|
207
|
+
|
208
|
+
---
|
209
|
+
|
210
|
+
### 3.1) Persistence methods
|
211
|
+
|
212
|
+
Methods | Behaviour | Arguments | Return
|
213
|
+
------- | --------- | ------ | ---------
|
214
|
+
.create | Generates a new instance of the class with *attributes and calls #save with the rest of its arguments| Hash attributes = nil, String relative_path = nil, Hash data = {}, Hash options = {} | Class instance
|
215
|
+
#new_record? | Returns @new_record if defined, else populates it with true if #id is present or false if blank. | - | Boolean
|
216
|
+
#destroyed?| Returns @destroyed if defined, else populates it with false. | - | Boolean
|
217
|
+
#persisted?| Returns true if both #new_record? and #destroyed? return false, else returns false. | - | Boolean
|
218
|
+
#save | if #new_record? makes a HTTP POST, else a PUT call. If !#new_record? and relative_path is blank, sets relative_path = id.to_s. If the server POST response is positive, sets @new_record = false. See 4.2) for more behaviour info. | String relative_path = nil, Hash data = {}, Hash options = {} | Boolean or Nil
|
219
|
+
#save! | Executes the same behaviour as #save, but will raise RecordNotSaved if the returning value is not true | String relative_path = nil, Hash data = {}, Hash options = {} | Boolean or Nil
|
220
|
+
#destroy | Does nothing if !persisted? else makes a HTTP DELETE call. If server response it positive, sets @destroyed = true. If relative_path is blank, sets relative_path = id.to_s. See 4.2) for more behaviour info. | String relative_path = nil, Hash data = {}, Hash options = {} | Boolean or Nil
|
221
|
+
|
222
|
+
---
|
223
|
+
|
224
|
+
### 3.2) Finder methods
|
225
|
+
|
226
|
+
Methods | Behaviour | Arguments | Return
|
227
|
+
------- | --------- | ------ | ---------
|
228
|
+
.all | calls .find(:all, data, options) | Hash data = {}, Hash options = {} | Class instance, Array of Class instances or an ArrayWithMetaData instance
|
229
|
+
.find | If relative_path == :all, sets relative_path = ''. Makes a Get call and initiates Class objects with the server's response data. See 4.3) and 4.4) for more behaviour info. | String relative_path, Hash data = {}, Hash options = {} | Class instance, Array of Class instances or an ArrayWithMetaData instance
|
230
|
+
|
231
|
+
---
|
232
|
+
|
233
|
+
### 3.3) Operator methods
|
234
|
+
...
|
235
|
+
|
236
|
+
---
|
237
|
+
|
238
|
+
### 3.3) Remote call methods
|
239
|
+
...
|
240
|
+
|
241
|
+
---
|
242
|
+
|
243
|
+
## 4) Behaviours
|
244
|
+
|
245
|
+
---
|
246
|
+
|
247
|
+
### 4.1) Delegation behaviour
|
248
|
+
...
|
249
|
+
|
250
|
+
---
|
251
|
+
|
252
|
+
### 4.2) Persistent operator behaviour
|
253
|
+
...
|
254
|
+
|
255
|
+
---
|
256
|
+
|
257
|
+
### 4.3) Operator behaviour
|
258
|
+
...
|
259
|
+
|
260
|
+
---
|
261
|
+
|
262
|
+
### 4.4) Remote call behaviour
|
263
|
+
...
|
264
|
+
|
265
|
+
---
|
266
|
+
|
267
|
+
## 4) TODO
|
268
|
+
|
269
|
+
1. Finish "Methods" and "Behaviours" documentation;
|
270
|
+
2. Allow changing the HTTP verb for a specific connection;
|
271
|
+
3. FinderMethods specs;
|
272
|
+
4. serialization_specs to test the json options for nested classes;
|
273
|
+
5. model_schema_specs;
|
274
|
+
6. Cache.
|
@@ -8,7 +8,7 @@ module SmoothOperator
|
|
8
8
|
|
9
9
|
attr_reader :meta_data, :internal_array, :table_name, :object_class
|
10
10
|
|
11
|
-
def_delegators :internal_array, :length,
|
11
|
+
def_delegators :internal_array, :length, :<<, :[]
|
12
12
|
|
13
13
|
def initialize(attributes, table_name, object_class)
|
14
14
|
_attributes = attributes.dup
|
@@ -12,7 +12,7 @@ module SmoothOperator
|
|
12
12
|
relative_path = '' if relative_path == :all
|
13
13
|
|
14
14
|
get(relative_path, data, options).tap do |remote_call|
|
15
|
-
remote_call.object = build_object(remote_call.parsed_response, options)
|
15
|
+
remote_call.object = build_object(remote_call.parsed_response, options) if remote_call.success?
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -28,7 +28,7 @@ module SmoothOperator
|
|
28
28
|
attr_writer :table_name
|
29
29
|
|
30
30
|
def table_name
|
31
|
-
@table_name ||= self.model_name.to_s.
|
31
|
+
@table_name ||= self.model_name.to_s.underscore.pluralize
|
32
32
|
end
|
33
33
|
|
34
34
|
def schema(structure)
|
@@ -49,19 +49,23 @@ module SmoothOperator
|
|
49
49
|
if defined? ActiveModel
|
50
50
|
rails_model_name_method
|
51
51
|
else
|
52
|
-
name.split('::').last.underscore.capitalize
|
52
|
+
@_model_name_namespace ||= name.split('::').last.underscore.capitalize
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
+
def model_name=(name)
|
57
|
+
@_model_name_namespace = name
|
58
|
+
end
|
56
59
|
|
60
|
+
|
57
61
|
protected ############## PROTECTED #############
|
58
62
|
|
59
63
|
def rails_model_name_method
|
60
64
|
@_model_name ||= begin
|
61
|
-
|
65
|
+
@_model_name_namespace ||= self.parents.detect do |n|
|
62
66
|
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
|
63
67
|
end
|
64
|
-
ActiveModel::Name.new(self,
|
68
|
+
ActiveModel::Name.new(self, @_model_name_namespace)
|
65
69
|
end
|
66
70
|
end
|
67
71
|
|
@@ -29,7 +29,7 @@ module SmoothOperator
|
|
29
29
|
url, timeout = (options[:endpoint] || self.endpoint), (options[:timeout] || self.timeout)
|
30
30
|
|
31
31
|
Faraday.new(url: url) do |builder|
|
32
|
-
builder.options.params_encoder = Faraday::NestedParamsEncoder # to properly encode arrays
|
32
|
+
# builder.options.params_encoder = Faraday::NestedParamsEncoder # to properly encode arrays
|
33
33
|
builder.options.timeout = timeout unless Helpers.blank?(timeout)
|
34
34
|
builder.request :url_encoded
|
35
35
|
builder.adapter adapter
|
@@ -38,7 +38,7 @@ module SmoothOperator
|
|
38
38
|
|
39
39
|
|
40
40
|
protected ################ PROTECTED ################
|
41
|
-
|
41
|
+
#COMPLEX
|
42
42
|
def make_the_call(http_verb, relative_path = '', data = {}, options = {})
|
43
43
|
params, body = strip_params(http_verb, data)
|
44
44
|
|
@@ -58,7 +58,8 @@ module SmoothOperator
|
|
58
58
|
end
|
59
59
|
|
60
60
|
RemoteCall::Base.new(response)
|
61
|
-
rescue Faraday::ConnectionFailed
|
61
|
+
# rescue Faraday::ConnectionFailed
|
62
|
+
rescue Faraday::Error::ConnectionFailed
|
62
63
|
RemoteCall::ConnectionFailed.new
|
63
64
|
end
|
64
65
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module SmoothOperator
|
2
|
-
|
2
|
+
|
3
3
|
module Persistence
|
4
4
|
|
5
5
|
def self.included(base)
|
@@ -15,13 +15,13 @@ module SmoothOperator
|
|
15
15
|
[:create, :save, :destroy].each do |method|
|
16
16
|
define_method("#{method}_http_verb=") { |http_verb| methods_http_verbs[method] = http_verb }
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def create(attributes = nil, relative_path = nil, data = {}, options = {})
|
20
|
-
if attributes.is_a?(Array)
|
21
|
-
|
22
|
-
else
|
20
|
+
# if attributes.is_a?(Array)
|
21
|
+
# attributes.map { |array_entry| create(array_entry, relative_path, data, options) }
|
22
|
+
# else
|
23
23
|
new(attributes).tap { |object| object.save(relative_path, data, options) }
|
24
|
-
end
|
24
|
+
# end
|
25
25
|
end
|
26
26
|
|
27
27
|
end
|
@@ -29,16 +29,14 @@ module SmoothOperator
|
|
29
29
|
|
30
30
|
def new_record?
|
31
31
|
return @new_record if defined?(@new_record)
|
32
|
-
|
32
|
+
|
33
33
|
@new_record = Helpers.blank?(get_internal_data("id"))
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
def destroyed?
|
37
|
-
@destroyed
|
38
|
-
end
|
37
|
+
return @destroyed if defined?(@destroyed)
|
39
38
|
|
40
|
-
|
41
|
-
@last_remote_call
|
39
|
+
@destroyed = false
|
42
40
|
end
|
43
41
|
|
44
42
|
def persisted?
|
@@ -56,8 +54,8 @@ module SmoothOperator
|
|
56
54
|
def destroy(relative_path = nil, data = {}, options = {})
|
57
55
|
return false unless persisted?
|
58
56
|
|
59
|
-
relative_path =
|
60
|
-
|
57
|
+
relative_path = id.to_s if Helpers.blank?(relative_path)
|
58
|
+
|
61
59
|
success = make_remote_call(self.class.methods_http_verbs[:destroy], relative_path, data, options)
|
62
60
|
|
63
61
|
@destroyed = true if success
|
@@ -81,7 +79,7 @@ module SmoothOperator
|
|
81
79
|
end
|
82
80
|
|
83
81
|
def update(relative_path, data, options)
|
84
|
-
relative_path =
|
82
|
+
relative_path = id.to_s if Helpers.blank?(relative_path)
|
85
83
|
|
86
84
|
make_remote_call(self.class.methods_http_verbs[:save], relative_path, data, options)
|
87
85
|
end
|
@@ -95,7 +93,7 @@ module SmoothOperator
|
|
95
93
|
@last_remote_call = self.class.send(http_verb, relative_path, data, options)
|
96
94
|
|
97
95
|
returning_data = @last_remote_call.parsed_response
|
98
|
-
|
96
|
+
|
99
97
|
if !@last_remote_call.error? && returning_data.is_a?(Hash)
|
100
98
|
assign_attributes returning_data.include?(model_name) ? returning_data[model_name] : returning_data
|
101
99
|
end
|
@@ -105,7 +103,7 @@ module SmoothOperator
|
|
105
103
|
|
106
104
|
def build_remote_call_args(http_verb, data, options)
|
107
105
|
return [data, options] if http_verb == :delete
|
108
|
-
|
106
|
+
|
109
107
|
hash = serializable_hash(options[:serializable_options]).dup
|
110
108
|
hash.delete('id')
|
111
109
|
|
@@ -113,5 +111,5 @@ module SmoothOperator
|
|
113
111
|
end
|
114
112
|
|
115
113
|
end
|
116
|
-
|
114
|
+
|
117
115
|
end
|
@@ -14,6 +14,8 @@ module SmoothOperator
|
|
14
14
|
|
15
15
|
def_delegators :response, :success?, :headers, :body
|
16
16
|
|
17
|
+
alias :ok? :success?
|
18
|
+
|
17
19
|
def initialize(response, object_class = nil)
|
18
20
|
@response, @object_class = response, object_class
|
19
21
|
end
|
@@ -53,7 +55,7 @@ module SmoothOperator
|
|
53
55
|
|
54
56
|
class ConnectionFailed
|
55
57
|
|
56
|
-
attr_reader :data, :object, :objects, :status, :headers, :body
|
58
|
+
attr_reader :data, :object, :objects, :parsed_response, :status, :headers, :body
|
57
59
|
|
58
60
|
def http_status; 0; end
|
59
61
|
|
@@ -67,4 +69,4 @@ module SmoothOperator
|
|
67
69
|
|
68
70
|
end
|
69
71
|
|
70
|
-
end
|
72
|
+
end
|
@@ -6,7 +6,8 @@ module SmoothOperator
|
|
6
6
|
Helpers.symbolyze_keys serializable_hash(options)
|
7
7
|
end
|
8
8
|
|
9
|
-
alias :attributes :to_hash
|
9
|
+
# alias :attributes :to_hash
|
10
|
+
def attributes; to_hash; end
|
10
11
|
|
11
12
|
def to_json(options = nil)
|
12
13
|
require 'json' unless defined? JSON
|
@@ -18,9 +19,25 @@ module SmoothOperator
|
|
18
19
|
send(attribute)
|
19
20
|
end
|
20
21
|
|
21
|
-
def serializable_hash(options = nil)
|
22
|
+
def serializable_hash(options = nil)
|
23
|
+
hash = {}
|
22
24
|
options ||= {}
|
23
25
|
|
26
|
+
attribute_names(options).each do |attribute_name|
|
27
|
+
hash[attribute_name] = read_attribute_for_hashing(attribute_name, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
method_names(options).each do |method_name|
|
31
|
+
hash[method_name.to_s] = send(method_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
hash
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
protected ##################### PROTECTED ###################
|
39
|
+
#COMPLEX
|
40
|
+
def attribute_names(options)
|
24
41
|
attribute_names = internal_data.keys.sort
|
25
42
|
|
26
43
|
if only = options[:only]
|
@@ -29,18 +46,12 @@ module SmoothOperator
|
|
29
46
|
attribute_names -= [*except].map(&:to_s)
|
30
47
|
end
|
31
48
|
|
32
|
-
|
33
|
-
|
34
|
-
hash = {}
|
35
|
-
|
36
|
-
attribute_names.each { |attribute_name| hash[attribute_name] = read_attribute_for_hashing(attribute_name, options) }
|
37
|
-
method_names.each { |method_name| hash[method_name.to_s] = send(method_name) }
|
38
|
-
|
39
|
-
hash
|
49
|
+
attribute_names
|
40
50
|
end
|
41
51
|
|
42
|
-
|
43
|
-
|
52
|
+
def method_names(options)
|
53
|
+
[*options[:methods]].select { |n| respond_to?(n) }
|
54
|
+
end
|
44
55
|
|
45
56
|
def read_attribute_for_hashing(attribute_name, options)
|
46
57
|
object = read_attribute_for_serialization(attribute_name)
|
@@ -11,22 +11,22 @@ module SmoothOperator
|
|
11
11
|
value.to_s
|
12
12
|
|
13
13
|
when :int, :integer, Integer, Fixnum
|
14
|
-
|
14
|
+
to_int(value)
|
15
15
|
|
16
16
|
when :date, Date
|
17
|
-
|
17
|
+
to_date(value)
|
18
18
|
|
19
19
|
when :float, Float
|
20
|
-
|
20
|
+
to_float(value)
|
21
21
|
|
22
22
|
when :bool, :boolean
|
23
|
-
|
23
|
+
to_boolean(value)
|
24
24
|
|
25
25
|
when :datetime, :date_time, DateTime
|
26
|
-
|
26
|
+
to_datetime(value)
|
27
27
|
|
28
28
|
else
|
29
|
-
value
|
29
|
+
Helpers.duplicate(value)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
data/smooth_operator.gemspec
CHANGED
@@ -24,5 +24,6 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
25
25
|
|
26
26
|
spec.add_dependency "json"
|
27
|
-
spec.add_dependency 'faraday', '> 0.9.0.rc5'
|
27
|
+
# spec.add_dependency 'faraday', '> 0.9.0.rc5'
|
28
|
+
spec.add_dependency 'faraday', '~> 0.8.1'
|
28
29
|
end
|
@@ -1,11 +1,56 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe SmoothOperator::FinderMethods do
|
4
|
-
subject {
|
4
|
+
subject { UserWithAddressAndPosts::Son }
|
5
5
|
|
6
|
+
describe ".all" do
|
7
|
+
xit "should can .find(:all) with the same arguments that it as received" do
|
8
|
+
|
9
|
+
end
|
10
|
+
end
|
6
11
|
|
7
12
|
describe ".find" do
|
8
|
-
|
13
|
+
context "when the server returns a single hash" do
|
14
|
+
it "it should return a RemoteCall instance with a subject's class instance populated with the returned hash" do
|
15
|
+
remote_call = subject.find(5)
|
16
|
+
user = remote_call.object
|
17
|
+
|
18
|
+
expect(user).to be_instance_of(subject)
|
19
|
+
expect(user.attributes).to eq(attributes_for(:user_with_address_and_posts))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when the server returns an array" do
|
24
|
+
it "it should return a RemoteCall instance an array that contains a subject's class instance, one for every array's entry" do
|
25
|
+
remote_call = subject.find(:all)
|
26
|
+
users = remote_call.objects
|
27
|
+
|
28
|
+
expect(users).to be_instance_of(Array)
|
29
|
+
expect(users[0]).to be_instance_of(subject)
|
30
|
+
expect(users[1]).to be_instance_of(subject)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "if any of the array entries is not a hash, it shall not be converted or alteread" do
|
34
|
+
remote_call = subject.find('misc_array')
|
35
|
+
users = remote_call.objects
|
36
|
+
|
37
|
+
expect(users).to be_instance_of(Array)
|
38
|
+
expect(users[0]).to be_instance_of(subject)
|
39
|
+
expect(users[1]).to be(2)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when the server returns a hash with a key (equal to subject's call.table_name) containing an array" do
|
44
|
+
it "it should return a RemoteCall instance an instance of ArrayWithMetaData" do
|
45
|
+
remote_call = subject.find('with_metadata')
|
46
|
+
users = remote_call.data
|
47
|
+
|
48
|
+
expect(users).to be_instance_of(SmoothOperator::ArrayWithMetaData)
|
49
|
+
expect(users.page).to be(1)
|
50
|
+
expect(users.total).to be(6)
|
51
|
+
users.each { |user| expect(user).to be_instance_of(subject) }
|
52
|
+
end
|
53
|
+
end
|
9
54
|
end
|
10
55
|
|
11
56
|
end
|
@@ -24,6 +24,7 @@ shared_examples_for "persistent remote call" do
|
|
24
24
|
|
25
25
|
it "it should return true" do
|
26
26
|
execute_method
|
27
|
+
expect(subject.last_remote_call.success?).to be true
|
27
28
|
expect(subject.last_remote_call.status).to be true
|
28
29
|
end
|
29
30
|
|
@@ -40,6 +41,7 @@ shared_examples_for "persistent remote call" do
|
|
40
41
|
|
41
42
|
it "it should return false" do
|
42
43
|
execute_method
|
44
|
+
expect(subject.last_remote_call.failure?).to be true
|
43
45
|
expect(subject.last_remote_call.status).to be false
|
44
46
|
end
|
45
47
|
|
@@ -56,6 +58,7 @@ shared_examples_for "persistent remote call" do
|
|
56
58
|
|
57
59
|
it "it should return nil" do
|
58
60
|
execute_method
|
61
|
+
expect(subject.last_remote_call.error?).to be true
|
59
62
|
expect(subject.last_remote_call.status).to be_nil
|
60
63
|
end
|
61
64
|
|
@@ -89,10 +92,10 @@ describe SmoothOperator::Persistence, helpers: :persistence do
|
|
89
92
|
|
90
93
|
describe ".create" do
|
91
94
|
|
95
|
+
subject { created_subject }
|
92
96
|
let(:method_arguments) { [] }
|
93
97
|
|
94
98
|
context "when attributes DON'T contain an ID" do
|
95
|
-
subject { created_subject }
|
96
99
|
let(:method_to_execute) { :create_without_id }
|
97
100
|
let(:persistence_state) { { 200 => true, 422 => false, 500 => false } }
|
98
101
|
|
@@ -107,7 +110,6 @@ describe SmoothOperator::Persistence, helpers: :persistence do
|
|
107
110
|
end
|
108
111
|
|
109
112
|
context "when attributes contain an ID" do
|
110
|
-
subject { created_subject }
|
111
113
|
let(:method_to_execute) { :create_with_id }
|
112
114
|
let(:persistence_state) { { 200 => true, 422 => true, 500 => true } }
|
113
115
|
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe SmoothOperator::RemoteCall do
|
4
|
+
subject { User }
|
5
|
+
|
6
|
+
describe "#success?" do
|
7
|
+
context "when the server response has a http code in the 200 range" do
|
8
|
+
it "should return true" do
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "when the server response has a http code NOT in the 200 range" do
|
13
|
+
it "should return false" do
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when the server response connection fails" do
|
18
|
+
it "should return false" do
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#failure?" do
|
24
|
+
context "when the server response has a http code in the 400 range" do
|
25
|
+
it "should return true" do
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when the server response has a http code NOT in the 400 range" do
|
30
|
+
it "should return false" do
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when the server response connection fails" do
|
35
|
+
it "should return false" do
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#error?" do
|
41
|
+
context "when the server response has a http code in the 500 range" do
|
42
|
+
it "should return true" do
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when the server response has a http code NOT in the 500 range" do
|
47
|
+
it "should return false" do
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when the server response connection fails" do
|
52
|
+
it "should return true" do
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#parsed_response" do
|
58
|
+
context "when the server response's body contains valid json data" do
|
59
|
+
it "should return the parsed result of that data" do
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when the server response's body does not contains valid json data" do
|
64
|
+
it "should return nil" do
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#status" do
|
70
|
+
context "when the #error? returns true" do
|
71
|
+
it "should return nil" do
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when the #error? returns false and #success? returns true" do
|
76
|
+
it "should return true" do
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when the #error? returns false and #success? returns false" do
|
81
|
+
it "should return false" do
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#http_status" do
|
87
|
+
context "when a server connection is established" do
|
88
|
+
it "should return the server's http response code" do
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when a server connection fails" do
|
93
|
+
it "should return 0" do
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe SmoothOperator::Validations do
|
4
|
+
subject { User.new(attributes_for(:user)) }
|
5
|
+
|
6
|
+
describe "#valid?" do
|
7
|
+
context "when executing a persistence method, and the server response has a hash with the key 'errors'" do
|
8
|
+
before { subject.save('send_error') }
|
9
|
+
|
10
|
+
it "it should return false" do
|
11
|
+
expect(subject.valid?).to be false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when executing a persistence method, and the server response does NOT have a hash with the key 'errors'" do
|
16
|
+
before { subject.save }
|
17
|
+
|
18
|
+
it "it should return true" do
|
19
|
+
expect(subject.valid?).to be true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#invalid?" do
|
25
|
+
context "when executing a persistence method, and the server response has a hash with the key 'errors'" do
|
26
|
+
before { subject.save('send_error') }
|
27
|
+
|
28
|
+
it "it should return true" do
|
29
|
+
expect(subject.invalid?).to be true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when executing a persistence method, and the server response does NOT have a hash with the key 'errors'" do
|
34
|
+
before { subject.save }
|
35
|
+
|
36
|
+
it "it should return false" do
|
37
|
+
expect(subject.invalid?).to be false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/test_server.rb
CHANGED
@@ -25,7 +25,16 @@ class TestServer < Sinatra::Base
|
|
25
25
|
|
26
26
|
|
27
27
|
get '/users' do
|
28
|
-
|
28
|
+
users = [FactoryGirl.attributes_for(:user_with_address_and_posts), FactoryGirl.attributes_for(:user_with_address_and_posts)]
|
29
|
+
|
30
|
+
users[0][:id] = 1
|
31
|
+
users[1][:id] = 2
|
32
|
+
|
33
|
+
json users
|
34
|
+
end
|
35
|
+
|
36
|
+
get '/users/misc_array' do
|
37
|
+
json [FactoryGirl.attributes_for(:user_with_address_and_posts), 2]
|
29
38
|
end
|
30
39
|
|
31
40
|
get '/users/with_metadata' do
|
@@ -37,6 +46,11 @@ class TestServer < Sinatra::Base
|
|
37
46
|
json FactoryGirl.attributes_for(:user_with_address_and_posts)
|
38
47
|
end
|
39
48
|
|
49
|
+
put '/users/send_error' do
|
50
|
+
data_with_error = { id: 1, errors: { first_name: ["can't be blank"] } }
|
51
|
+
json data_with_error
|
52
|
+
end
|
53
|
+
|
40
54
|
|
41
55
|
post '/users' do
|
42
56
|
common_response
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smooth_operator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.8.
|
4
|
+
version: 1.8.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- João Gonçalves
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -42,16 +42,16 @@ dependencies:
|
|
42
42
|
name: faraday
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ~>
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.
|
47
|
+
version: 0.8.1
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
54
|
+
version: 0.8.1
|
55
55
|
description: ActiveResource alternative
|
56
56
|
email:
|
57
57
|
- goncalves.joao@gmail.com
|
@@ -91,7 +91,9 @@ files:
|
|
91
91
|
- spec/smooth_operator/model_schema_spec.rb
|
92
92
|
- spec/smooth_operator/operator_spec.rb
|
93
93
|
- spec/smooth_operator/persistence_spec.rb
|
94
|
+
- spec/smooth_operator/remote_call_spec.rb
|
94
95
|
- spec/smooth_operator/serialization_spec.rb
|
96
|
+
- spec/smooth_operator/validations_spec.rb
|
95
97
|
- spec/spec_helper.rb
|
96
98
|
- spec/support/helpers/persistence_helper.rb
|
97
99
|
- spec/support/localhost_server.rb
|
@@ -133,7 +135,9 @@ test_files:
|
|
133
135
|
- spec/smooth_operator/model_schema_spec.rb
|
134
136
|
- spec/smooth_operator/operator_spec.rb
|
135
137
|
- spec/smooth_operator/persistence_spec.rb
|
138
|
+
- spec/smooth_operator/remote_call_spec.rb
|
136
139
|
- spec/smooth_operator/serialization_spec.rb
|
140
|
+
- spec/smooth_operator/validations_spec.rb
|
137
141
|
- spec/spec_helper.rb
|
138
142
|
- spec/support/helpers/persistence_helper.rb
|
139
143
|
- spec/support/localhost_server.rb
|