served 0.1.12 → 0.2.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/.ruby-version +1 -0
- data/.travis.yml +3 -2
- data/CHANGELOG.md +16 -1
- data/README.md +8 -1
- data/lib/served/attribute/base.rb +7 -3
- data/lib/served/backends/base.rb +16 -0
- data/lib/served/backends/http.rb +49 -0
- data/lib/served/backends/httparty.rb +47 -0
- data/lib/served/backends/patron.rb +45 -0
- data/lib/served/backends.rb +21 -0
- data/lib/served/config.rb +12 -0
- data/lib/served/http_client.rb +18 -27
- data/lib/served/{support → resource}/attributable.rb +30 -6
- data/lib/served/resource/base.rb +59 -65
- data/lib/served/resource/configurable.rb +52 -0
- data/lib/served/{support → resource}/serializable.rb +26 -8
- data/lib/served/{support → resource}/validatable.rb +25 -7
- data/lib/served/resource.rb +1 -1
- data/lib/served/version.rb +1 -1
- data/lib/served.rb +4 -8
- data/served.gemspec +7 -4
- metadata +60 -25
- data/lib/served/support.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20a3772be5d8a3b6c27909c4cb61c4bdd33fdb2c
|
4
|
+
data.tar.gz: 3bc802236bc9a3f77bdab3577bb917313fc29332
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b9c306b2e26bd19e0fdfef7ce4abd9c14172c2490030710613c44041787e3ae07ff8dd47d51975163eefa405dd0249cd43861bb115d8df3a9ba1471517b5973
|
7
|
+
data.tar.gz: 3f45537c4946005cf8e056db0d9832e95821ba21ce8c47c2edea36582c878eeb2bf186e56b0aff7fcbd535bb6fec79dbfd8bac04a0688140193a3a24487c5aae
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.0
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,26 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.2.0
|
4
|
+
|
5
|
+
* new HTTP client backend functionality, ability to support multiple
|
6
|
+
HTTP backends
|
7
|
+
* `headers` updated to merge, can be called multiple times, allows modularization
|
8
|
+
* backend support for `HTTParty`, `HTTP`, and `Patron`
|
9
|
+
* `host_config` method removed from `Support::Resource::Base`
|
10
|
+
* update travis to test against Ruby 2.4.0, Ruby 2.1.9, Ruby 2.3.2
|
11
|
+
* various refactoring
|
12
|
+
* add `validation_on_save` option to allow skipping validation on save
|
13
|
+
* better configuration inheritance in resources
|
14
|
+
* if using HTTParty as a backend `Errno::ECONNREFUSED` will no longer be returned for connection errors,
|
15
|
+
`Served::HTTPClient::ConnectionFailed` will be raised instead
|
16
|
+
|
3
17
|
## 0.1.12
|
4
18
|
* add validation support
|
5
19
|
* add serialization support
|
6
20
|
* allow definition of individual attributes
|
7
21
|
* add resource level `headers` option
|
8
|
-
*
|
22
|
+
* add resource level `reasource_name` option
|
23
|
+
* add resource level `host` option, `host_config` will be deprecated in 0.2.0
|
9
24
|
* add `Served::Attribute::Base` class
|
10
25
|
|
11
26
|
## 0.1.11
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Served
|
2
|
-
[](https://travis-ci.org/optoro/served)
|
2
|
+
[](https://travis-ci.org/optoro/served)
|
3
3
|
[](https://badge.fury.io/rb/served)
|
4
4
|
|
5
5
|
Served is a gem in the vein of [ActiveResource](https://github.com/rails/activeresource) designed to facilitate
|
@@ -11,6 +11,7 @@ Add the following to your Gemfile:
|
|
11
11
|
|
12
12
|
```gem 'served'```
|
13
13
|
|
14
|
+
Served supports Ruby versions `>= 2.1` and versions of Rails `>= 3.2`, including Rails 5.
|
14
15
|
# Configuration
|
15
16
|
Served is configured by passing a block to ```Served::configure```.
|
16
17
|
|
@@ -21,6 +22,8 @@ Served.configure do |config|
|
|
21
22
|
}
|
22
23
|
|
23
24
|
config.timeout = 100
|
25
|
+
|
26
|
+
config.backend = :patron
|
24
27
|
end
|
25
28
|
```
|
26
29
|
|
@@ -43,6 +46,10 @@ maintained for backwards compatibility, however the extension will likely be rem
|
|
43
46
|
## Timeout
|
44
47
|
Sets the request timeout in milliseconds.
|
45
48
|
|
49
|
+
## Backend
|
50
|
+
Configure the HTTP client backend. Supports either :http (default), which will use the HTTP client, or :patron, which
|
51
|
+
will use Patron. Patron is suggested for use if high concurrency between requests is required. Also requires the
|
52
|
+
machine to have libcurl.
|
46
53
|
|
47
54
|
# Defining a Resource
|
48
55
|
A service model can be created by declaring a class inheriting from ```Service::Resource::Base```.
|
@@ -1,9 +1,13 @@
|
|
1
1
|
module Served
|
2
2
|
module Attribute
|
3
3
|
class Base
|
4
|
-
include
|
5
|
-
include
|
6
|
-
include
|
4
|
+
include Resource::Attributable
|
5
|
+
include Resource::Serializable
|
6
|
+
include Resource::Validatable
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
# placeholder
|
10
|
+
end
|
7
11
|
end
|
8
12
|
end
|
9
13
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'http'
|
2
|
+
module Served
|
3
|
+
module Backends
|
4
|
+
#HTTP Backend uses {https://github.com/httprb/http HTTP} client library.
|
5
|
+
class HTTP < Base
|
6
|
+
|
7
|
+
def get(endpoint, id, params={})
|
8
|
+
response = ::HTTP
|
9
|
+
.timeout(global: timeout)
|
10
|
+
.headers(headers)
|
11
|
+
.get(template.expand(id: id, query: params, resource: endpoint).to_s)
|
12
|
+
serialize_response(response)
|
13
|
+
rescue ::HTTP::ConnectionError
|
14
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
15
|
+
end
|
16
|
+
|
17
|
+
def put(endpoint, id, body, params={})
|
18
|
+
response = ::HTTP
|
19
|
+
.timeout(global: timeout)
|
20
|
+
.headers(headers)
|
21
|
+
.put(template.expand(id: id, query: params, resource: endpoint).to_s, body: body)
|
22
|
+
serialize_response(response)
|
23
|
+
rescue ::HTTP::ConnectionError
|
24
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
25
|
+
end
|
26
|
+
|
27
|
+
def post(endpoint, body, params={})
|
28
|
+
response = ::HTTP
|
29
|
+
.timeout(global: timeout)
|
30
|
+
.headers(headers)
|
31
|
+
.post(template.expand(query: params, resource: endpoint).to_s, body: body)
|
32
|
+
serialize_response(response)
|
33
|
+
rescue ::HTTP::ConnectionError
|
34
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete(endpoint, id, params={})
|
38
|
+
response = ::HTTP
|
39
|
+
.timeout(global: timeout)
|
40
|
+
.headers(headers)
|
41
|
+
.delete(template.expand(query: params, resource: endpoint, id: id).to_s)
|
42
|
+
serialize_response(response)
|
43
|
+
rescue ::HTTP::ConnectionError
|
44
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
module Served
|
3
|
+
module Backends
|
4
|
+
# HTTParty Backend uses the {https://github.com/jnunemaker/httparty HTTParty} client
|
5
|
+
class HTTParty < Base
|
6
|
+
|
7
|
+
def get(endpoint, id, params={})
|
8
|
+
::HTTParty.get(template.expand(id: id, query: params, resource: endpoint).to_s,
|
9
|
+
headers: headers,
|
10
|
+
timeout: timeout
|
11
|
+
)
|
12
|
+
rescue Errno::ECONNREFUSED
|
13
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
14
|
+
end
|
15
|
+
|
16
|
+
def put(endpoint, id, body, params={})
|
17
|
+
::HTTParty.put(template.expand(id: id, query: params, resource: endpoint).to_s,
|
18
|
+
body: body,
|
19
|
+
headers: headers,
|
20
|
+
timeout: timeout
|
21
|
+
)
|
22
|
+
rescue Errno::ECONNREFUSED
|
23
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
24
|
+
end
|
25
|
+
|
26
|
+
def post(endpoint, body, params={})
|
27
|
+
::HTTParty.post(template.expand(query: params, resource: endpoint).to_s,
|
28
|
+
body: body,
|
29
|
+
headers: headers,
|
30
|
+
timeout: timeout
|
31
|
+
)
|
32
|
+
rescue Errno::ECONNREFUSED
|
33
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete(endpoint, id, params={})
|
37
|
+
::HTTParty.delete(template.expand(id: id, query: params, resource: endpoint).to_s,
|
38
|
+
headers: headers,
|
39
|
+
timeout: timeout
|
40
|
+
)
|
41
|
+
rescue Errno::ECONNREFUSED
|
42
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'patron'
|
2
|
+
module Served
|
3
|
+
module Backends
|
4
|
+
# Patron Backend uses {Patron https://github.com/toland/patron} for its client. This backend does not lock the GIL
|
5
|
+
# and is thread safe. Use Patron if you need high concurrency.
|
6
|
+
class Patron < Base
|
7
|
+
|
8
|
+
def get(endpoint, id, params={})
|
9
|
+
serialize_response(::Patron::Session.new(headers: headers, timeout: timeout)
|
10
|
+
.get(template.expand(id: id, query: params, resource: endpoint).to_s))
|
11
|
+
rescue ::Patron::ConnectionFailed
|
12
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
13
|
+
end
|
14
|
+
|
15
|
+
def put(endpoint, id, body, params={})
|
16
|
+
serialize_response(::Patron::Session.new(headers: headers, timeout: timeout)
|
17
|
+
.put(template.expand(id: id, query: params, resource: endpoint).to_s, body))
|
18
|
+
rescue ::Patron::ConnectionFailed
|
19
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
20
|
+
end
|
21
|
+
|
22
|
+
def post(endpoint, body, params={})
|
23
|
+
serialize_response(::Patron::Session.new(headers: headers, timeout: timeout)
|
24
|
+
.post(template.expand(query: params, resource: endpoint).to_s, body))
|
25
|
+
rescue ::Patron::ConnectionFailed
|
26
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete(endpoint, id, params={})
|
30
|
+
serialize_response(::Patron::Session.new(headers: headers, timeout: timeout)
|
31
|
+
.delete(template.expand(id: id, query: params, resource: endpoint).to_s))
|
32
|
+
rescue ::Patron::ConnectionFailed
|
33
|
+
raise Served::HTTPClient::ConnectionFailed.new(resource)
|
34
|
+
end
|
35
|
+
|
36
|
+
def serialize_response(response)
|
37
|
+
OpenStruct.new({
|
38
|
+
body: response.body,
|
39
|
+
code: response.status
|
40
|
+
})
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'backends/base'
|
2
|
+
module Served
|
3
|
+
module Backends
|
4
|
+
|
5
|
+
# @private
|
6
|
+
def self.[](backend)
|
7
|
+
@backends ||= {
|
8
|
+
http: 'HTTP',
|
9
|
+
patron: 'Patron',
|
10
|
+
httparty: 'HTTParty'
|
11
|
+
}
|
12
|
+
if @backends[backend]
|
13
|
+
require_relative "backends/#{backend}"
|
14
|
+
return self.const_get(@backends[backend].classify.to_sym)
|
15
|
+
end
|
16
|
+
require_relative 'backends/httparty'
|
17
|
+
self.const_get(@backends[:httparty].classify.to_sym)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/served/http_client.rb
CHANGED
@@ -1,38 +1,29 @@
|
|
1
1
|
require 'addressable/template'
|
2
2
|
module Served
|
3
|
-
# Provides an interface between
|
3
|
+
# Provides an interface between the HTTP client and the Resource.
|
4
4
|
class HTTPClient
|
5
|
-
|
5
|
+
|
6
6
|
DEFAULT_TEMPLATE = '{/resource*}{/id}.json{?query*}'
|
7
7
|
|
8
|
-
|
9
|
-
host += DEFAULT_TEMPLATE unless host =~ /{.+}/
|
10
|
-
@template = Addressable::Template.new(host)
|
11
|
-
@timeout = timeout
|
12
|
-
@headers = HEADERS.merge(headers || {})
|
13
|
-
end
|
8
|
+
attr_reader :template, :resource
|
14
9
|
|
15
|
-
|
16
|
-
|
17
|
-
headers: @headers,
|
18
|
-
timeout: @timeout
|
19
|
-
)
|
20
|
-
end
|
10
|
+
delegate :get, :put, :delete, :post, to: :@backend
|
11
|
+
delegate :headers, :timeout, :host, to: :@resource
|
21
12
|
|
22
|
-
|
23
|
-
HTTParty.put(@template.expand(id: id, query: params, resource: endpoint).to_s,
|
24
|
-
body: body,
|
25
|
-
headers: @headers,
|
26
|
-
timeout: @timeout
|
27
|
-
)
|
28
|
-
end
|
13
|
+
class ConnectionFailed < StandardError
|
29
14
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
15
|
+
def initialize(resource)
|
16
|
+
super "Resource #{resource.name} could not be reached on #{resource.host}"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(resource)
|
22
|
+
@resource = resource
|
23
|
+
h = host + @resource.template
|
24
|
+
@template = Addressable::Template.new(h)
|
25
|
+
@backend = Served::Backends[Served.config.backend].new(self)
|
36
26
|
end
|
27
|
+
|
37
28
|
end
|
38
29
|
end
|
@@ -1,8 +1,13 @@
|
|
1
1
|
module Served
|
2
|
-
module
|
2
|
+
module Resource
|
3
3
|
module Attributable
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
+
included do
|
7
|
+
prepend Prepend
|
8
|
+
singleton_class.prepend ClassMethods::Prepend
|
9
|
+
end
|
10
|
+
|
6
11
|
module ClassMethods
|
7
12
|
|
8
13
|
# declare an attribute for the resource
|
@@ -33,10 +38,31 @@ module Served
|
|
33
38
|
@attributes ||= {}
|
34
39
|
end
|
35
40
|
|
41
|
+
module Prepend
|
42
|
+
|
43
|
+
def inherited(subclass)
|
44
|
+
self.attributes.each do |name, options|
|
45
|
+
subclass.attribute name, options
|
46
|
+
end
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
36
52
|
end
|
37
53
|
|
38
|
-
|
39
|
-
|
54
|
+
module Prepend
|
55
|
+
|
56
|
+
def initialize(options={})
|
57
|
+
reload_with_attributes(options.symbolize_keys)
|
58
|
+
super options
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Array] the keys for all the defined attributes
|
64
|
+
def attributes
|
65
|
+
Hash[self.class.attributes.keys.collect { |name| [name, send(name)] }]
|
40
66
|
end
|
41
67
|
|
42
68
|
private
|
@@ -56,12 +82,10 @@ module Served
|
|
56
82
|
end
|
57
83
|
|
58
84
|
def set_attribute(name, value)
|
59
|
-
raise InvalidAttributeError, "`#{name}' is not a valid attribute" unless self.class.attributes.include?(name)
|
60
85
|
instance_variable_set("@#{name}", value)
|
61
86
|
end
|
62
87
|
|
63
88
|
end
|
64
89
|
|
65
|
-
|
66
90
|
end
|
67
|
-
end
|
91
|
+
end
|
data/lib/served/resource/base.rb
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
require_relative 'attributable'
|
2
|
+
require_relative 'serializable'
|
3
|
+
require_relative 'validatable'
|
4
|
+
require_relative 'configurable'
|
5
|
+
|
1
6
|
module Served
|
2
7
|
module Resource
|
3
8
|
# Service Resources should inherit directly from this class. Provides interfaces necessary for communicating with
|
@@ -12,34 +17,64 @@ module Served
|
|
12
17
|
# A resource may also serialize values as specific classes, including nested resources. If serialize is set to a
|
13
18
|
# Served Resource, it will validate the nested resource as well as the top level.
|
14
19
|
class Base
|
15
|
-
include
|
16
|
-
include
|
17
|
-
include
|
20
|
+
include Configurable
|
21
|
+
include Attributable
|
22
|
+
include Validatable
|
23
|
+
include Serializable
|
24
|
+
|
25
|
+
attribute :id
|
26
|
+
|
27
|
+
# Default headers for every request
|
28
|
+
HEADERS = {'Content-type' => 'application/json', 'Accept' => 'application/json'}
|
18
29
|
|
19
|
-
# raised when an attribute is passed to a resource that is not declared
|
20
|
-
class InvalidAttributeError < StandardError;
|
21
|
-
end
|
22
30
|
|
23
31
|
# raised when the connection receives a response from a service that does not constitute a 200
|
24
32
|
class ServiceError < StandardError
|
25
33
|
attr_reader :response
|
26
34
|
|
27
|
-
def initialize(response)
|
35
|
+
def initialize(resource, response)
|
28
36
|
@response = response
|
29
|
-
|
37
|
+
begin
|
38
|
+
error = JSON.parse(response.body)
|
39
|
+
rescue JSON::ParserError
|
40
|
+
super "Service #{resource.class.name} experienced an error and sent back an invalid error response"
|
41
|
+
return
|
42
|
+
end
|
43
|
+
super "Service #{resource.class.name} responded with an error: #{error['error']} -> #{error['exception']}"
|
44
|
+
set_backtrace(error['traces']['Full Trace'].collect {|e| e['trace']})
|
30
45
|
end
|
31
46
|
end
|
32
47
|
|
33
|
-
|
48
|
+
class_configurable :resource_name do
|
49
|
+
name.split('::').last.tableize
|
50
|
+
end
|
51
|
+
|
52
|
+
class_configurable :host do
|
53
|
+
Served.config[:hosts][parent.name.underscore.split('/')[-1]] || Served.config[:hosts][:default]
|
54
|
+
end
|
34
55
|
|
56
|
+
class_configurable :timeout do
|
57
|
+
Served.config.timeout
|
58
|
+
end
|
59
|
+
|
60
|
+
class_configurable :_headers do
|
61
|
+
HEADERS
|
62
|
+
end
|
63
|
+
|
64
|
+
class_configurable :template do
|
65
|
+
'{/resource*}{/id}.json{?query*}'
|
66
|
+
end
|
67
|
+
|
68
|
+
class << self
|
35
69
|
|
36
70
|
# Defines the default headers that should be used for the request.
|
37
71
|
#
|
38
72
|
# @param headers [Hash] the headers to send with each requesat
|
39
73
|
# @return headers [Hash] the default headers for the class
|
40
74
|
def headers(h={})
|
41
|
-
|
42
|
-
|
75
|
+
headers ||= _headers
|
76
|
+
_headers(headers.merge!(h)) unless h.empty?
|
77
|
+
_headers
|
43
78
|
end
|
44
79
|
|
45
80
|
# Looks up a resource on the service by id. For example `SomeResource.find(5)` would call `/some_resources/5`
|
@@ -51,62 +86,21 @@ module Served
|
|
51
86
|
instance.reload
|
52
87
|
end
|
53
88
|
|
54
|
-
#
|
55
|
-
#
|
56
|
-
# @param resource [String] the name of the resource
|
57
|
-
# @return [String] the name of the resource. `SomeResource.resource_name` will return `some_resources`
|
58
|
-
def resource_name(resource=nil)
|
59
|
-
@resource_name = resource if resource
|
60
|
-
@resource_name ||name.split('::').last.tableize
|
61
|
-
end
|
62
|
-
|
63
|
-
# @deprecated returns host information
|
64
|
-
def host_config
|
65
|
-
host
|
66
|
-
end
|
67
|
-
|
68
|
-
# Get or set the host for the resource
|
69
|
-
#
|
70
|
-
# @param host [String] the resource host
|
71
|
-
# @return [String] or [Hash] the configured host.
|
72
|
-
# @see Services::Configuration
|
73
|
-
def host(h=nil)
|
74
|
-
@host = h if h
|
75
|
-
@host ||= Served.config[:hosts][parent.name.underscore.split('/')[-1]]
|
76
|
-
end
|
77
|
-
|
78
|
-
# Get or set the timeout for the current resource
|
79
|
-
#
|
80
|
-
# @return [Integer] allowed timeout in seconds
|
81
|
-
def timeout(sec=nil)
|
82
|
-
@timeout = sec if sec
|
83
|
-
@timeout || Served.config.timeout
|
84
|
-
end
|
85
|
-
|
89
|
+
# @return [Served::HTTPClient] the HTTPClient using the configured backend
|
86
90
|
def client
|
87
|
-
@
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
|
92
|
-
# Everything should allow an id attribute
|
93
|
-
def inherited(subclass)
|
94
|
-
return if subclass.attributes.include?(:id) # attribute method does this already, but rather not do a
|
95
|
-
# class_eval if not necessary
|
96
|
-
subclass.class_eval do
|
97
|
-
attribute :id
|
98
|
-
end
|
91
|
+
@client ||= Served::HTTPClient.new(self)
|
99
92
|
end
|
100
93
|
|
101
94
|
end
|
102
95
|
|
103
|
-
|
104
|
-
|
105
|
-
self.class.resource_name
|
96
|
+
def initialize(options={})
|
97
|
+
# placeholder
|
106
98
|
end
|
107
99
|
|
108
100
|
# Saves the record to the service. Will call POST if the record does not have an id, otherwise will call PUT
|
109
101
|
# to update the record
|
102
|
+
#
|
103
|
+
# @return [Boolean] returns true or false depending on save success
|
110
104
|
def save
|
111
105
|
if id
|
112
106
|
reload_with_attributes(put[resource_name.singularize])
|
@@ -116,13 +110,9 @@ module Served
|
|
116
110
|
true
|
117
111
|
end
|
118
112
|
|
119
|
-
alias_method :save!, :save # TODO: differentiate save! and safe much the same AR does.
|
120
|
-
|
121
|
-
def attributes
|
122
|
-
Hash[self.class.attributes.keys.collect { |name| [name, send(name)] }]
|
123
|
-
end
|
124
|
-
|
125
113
|
# Reloads the resource using attributes from the service
|
114
|
+
#
|
115
|
+
# @return [self] self
|
126
116
|
def reload
|
127
117
|
reload_with_attributes(get)
|
128
118
|
self
|
@@ -145,7 +135,7 @@ module Served
|
|
145
135
|
end
|
146
136
|
|
147
137
|
def handle_response(response)
|
148
|
-
raise ServiceError, response unless (200..299).include?(response.code)
|
138
|
+
raise ServiceError.new(self, response) unless (200..299).include?(response.code)
|
149
139
|
JSON.parse(response.body)
|
150
140
|
end
|
151
141
|
|
@@ -153,6 +143,10 @@ module Served
|
|
153
143
|
self.class.client
|
154
144
|
end
|
155
145
|
|
146
|
+
def presenter
|
147
|
+
{resource_name.singularize.to_sym => attributes}
|
148
|
+
end
|
149
|
+
|
156
150
|
end
|
157
151
|
end
|
158
152
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Served
|
2
|
+
module Resource
|
3
|
+
module Configurable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
singleton_class.prepend ClassMethods::Prepend
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
module Prepend
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def inherited(subclass)
|
17
|
+
super
|
18
|
+
instance_variables.each do |v|
|
19
|
+
instance = instance_variable_get(v)
|
20
|
+
instance = instance.clone unless instance.is_a? Fixnum
|
21
|
+
subclass.send(:instance_variable_set, v, instance) if /@_c_/ =~ v
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Declare a configurable attribute. This is used to declare the configuration methods used in
|
30
|
+
# Served::Resource::Base
|
31
|
+
def class_configurable(name, options={}, &block)
|
32
|
+
instance_eval do
|
33
|
+
instance_variable_set(:"@_c_#{name}", options[:default]) if options[:default]
|
34
|
+
instance_variable_set(:"@_c_#{name}", block ) if block_given? && !instance_variable_get(:"@#{name}")
|
35
|
+
|
36
|
+
define_singleton_method(name) do |value=nil|
|
37
|
+
instance_variable_set(:"@_c_#{name}", value) if value
|
38
|
+
value = instance_variable_get(:"@_c_#{name}") unless value
|
39
|
+
return instance_eval &value if value.is_a? Proc
|
40
|
+
value
|
41
|
+
end
|
42
|
+
|
43
|
+
define_method(name) do
|
44
|
+
self.class.send(name)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,14 +1,29 @@
|
|
1
1
|
module Served
|
2
|
-
module
|
2
|
+
module Resource
|
3
3
|
module Serializable
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
+
# pseudo boolean class for serialization
|
7
|
+
unless Object.const_defined?(:Boolean)
|
8
|
+
class ::Boolean;
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
6
12
|
# Specialized class serializers
|
7
13
|
SERIALIZERS = {
|
8
14
|
Fixnum => {call: :to_i},
|
9
15
|
String => {call: :to_s},
|
10
|
-
Symbol => {call: :to_sym
|
11
|
-
|
16
|
+
Symbol => {call: :to_sym, converter: -> (value) {
|
17
|
+
if value.is_a? Array
|
18
|
+
value = value.map { |a| a.to_sym }
|
19
|
+
return value
|
20
|
+
end
|
21
|
+
}},
|
22
|
+
Float => {call: :to_f},
|
23
|
+
Boolean => {converter: -> (value) {
|
24
|
+
return false unless value == "true"
|
25
|
+
true
|
26
|
+
}}
|
12
27
|
}
|
13
28
|
|
14
29
|
included do
|
@@ -27,8 +42,12 @@ module Served
|
|
27
42
|
if serializer.is_a? Proc
|
28
43
|
value = serializer.call(value)
|
29
44
|
elsif s = SERIALIZERS[serializer]
|
30
|
-
|
31
|
-
|
45
|
+
called = false
|
46
|
+
if s[:call] && value.respond_to?(s[:call])
|
47
|
+
value = value.send(s[:call])
|
48
|
+
called = true
|
49
|
+
end
|
50
|
+
value = s[:converter].call(value) if s[:converter] && !called
|
32
51
|
else
|
33
52
|
value = serializer.new(value)
|
34
53
|
end
|
@@ -36,7 +55,6 @@ module Served
|
|
36
55
|
super
|
37
56
|
end
|
38
57
|
|
39
|
-
|
40
58
|
end
|
41
59
|
|
42
60
|
module ClassMethods
|
@@ -50,7 +68,7 @@ module Served
|
|
50
68
|
end
|
51
69
|
|
52
70
|
# renders the model as json
|
53
|
-
def to_json
|
71
|
+
def to_json(*args)
|
54
72
|
raise InvalidPresenter, 'Presenter must respond to #to_json' unless presenter.respond_to? :to_json
|
55
73
|
presenter.to_json
|
56
74
|
end
|
@@ -58,7 +76,7 @@ module Served
|
|
58
76
|
# override this to return a presenter to be used for serialization, otherwise all attributes will be
|
59
77
|
# serialized
|
60
78
|
def presenter
|
61
|
-
|
79
|
+
attributes
|
62
80
|
end
|
63
81
|
|
64
82
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Served
|
2
|
-
module
|
2
|
+
module Resource
|
3
3
|
# Resource validation functionality
|
4
4
|
module Validatable
|
5
5
|
extend ActiveSupport::Concern
|
@@ -10,23 +10,40 @@ module Served
|
|
10
10
|
:numericality,
|
11
11
|
:format,
|
12
12
|
:inclusion,
|
13
|
-
:confirmation
|
14
13
|
]
|
15
14
|
|
15
|
+
class ResourceInvalid < StandardError
|
16
|
+
|
17
|
+
def initialize(resource)
|
18
|
+
super "[#{resource.errors.full_messages.join(', ')}]"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Saves a resource and raises an error if the save fails.
|
23
|
+
def save!
|
24
|
+
raise ResourceInvalid.new(self) unless run_validations! && save(false)
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
16
28
|
included do
|
17
29
|
include ActiveModel::Validations
|
30
|
+
include Configurable
|
31
|
+
include Attributable
|
18
32
|
singleton_class.prepend ClassMethods::Prepend
|
19
33
|
prepend Prepend
|
34
|
+
|
35
|
+
class_configurable :validate_on_save do
|
36
|
+
true
|
37
|
+
end
|
20
38
|
end
|
21
39
|
|
22
40
|
module Prepend
|
23
41
|
|
24
|
-
def save
|
25
|
-
return false
|
26
|
-
super
|
42
|
+
def save(with_validations=true)
|
43
|
+
return false if (with_validations && self.class.validate_on_save && !valid?)
|
44
|
+
super()
|
27
45
|
end
|
28
46
|
|
29
|
-
|
30
47
|
protected
|
31
48
|
|
32
49
|
def run_validations!
|
@@ -41,6 +58,7 @@ module Served
|
|
41
58
|
end
|
42
59
|
|
43
60
|
module ClassMethods
|
61
|
+
|
44
62
|
module Prepend
|
45
63
|
|
46
64
|
def attribute(name, options={})
|
@@ -63,4 +81,4 @@ module Served
|
|
63
81
|
|
64
82
|
end
|
65
83
|
end
|
66
|
-
end
|
84
|
+
end
|
data/lib/served/resource.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require 'served/resource/base'
|
1
|
+
require 'served/resource/base'
|
data/lib/served/version.rb
CHANGED
data/lib/served.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'httparty'
|
2
1
|
require 'active_support/configurable'
|
3
2
|
require 'active_support/core_ext/string'
|
4
3
|
require 'active_support/core_ext/module'
|
@@ -6,14 +5,11 @@ require 'active_model'
|
|
6
5
|
|
7
6
|
require 'served/engine'
|
8
7
|
require 'served/version'
|
8
|
+
require 'served/config'
|
9
|
+
require 'served/backends'
|
9
10
|
require 'served/http_client'
|
10
|
-
require 'served/support'
|
11
11
|
require 'served/resource'
|
12
12
|
require 'served/attribute'
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
configure do |config|
|
17
|
-
config.timeout = 30
|
18
|
-
end
|
19
|
-
end
|
14
|
+
|
15
|
+
|
data/served.gemspec
CHANGED
@@ -19,12 +19,15 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
|
-
spec.add_dependency 'httparty'
|
23
22
|
spec.add_dependency 'activesupport', '>= 3.2'
|
24
23
|
spec.add_dependency 'addressable', '>= 2.4.0'
|
25
24
|
spec.add_dependency 'activemodel', '>= 3.2'
|
26
25
|
|
27
|
-
spec.add_development_dependency '
|
28
|
-
spec.add_development_dependency '
|
29
|
-
spec.add_development_dependency '
|
26
|
+
spec.add_development_dependency 'httparty', '~> 0.14.0'
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.10'
|
28
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.4.0'
|
30
|
+
spec.add_development_dependency 'http', '~> 1.0.4'
|
31
|
+
spec.add_development_dependency 'patron', '~> 0.5.0'
|
32
|
+
|
30
33
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: served
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jarod Reid
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: httparty
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: activesupport
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +52,20 @@ dependencies:
|
|
66
52
|
- - ">="
|
67
53
|
- !ruby/object:Gem::Version
|
68
54
|
version: '3.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: httparty
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.14.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.14.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: bundler
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,16 +98,44 @@ dependencies:
|
|
98
98
|
name: rspec
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- - "
|
101
|
+
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
103
|
+
version: 3.4.0
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- - "
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.4.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: http
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.0.4
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.0.4
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: patron
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.5.0
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
109
137
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
138
|
+
version: 0.5.0
|
111
139
|
description: Served provides an easy to use model layer for communicating with disributed
|
112
140
|
Rails based Services.
|
113
141
|
email:
|
@@ -118,6 +146,7 @@ extra_rdoc_files: []
|
|
118
146
|
files:
|
119
147
|
- ".gitignore"
|
120
148
|
- ".rspec"
|
149
|
+
- ".ruby-version"
|
121
150
|
- ".travis.yml"
|
122
151
|
- CHANGELOG.md
|
123
152
|
- CODE_OF_CONDUCT.md
|
@@ -133,14 +162,20 @@ files:
|
|
133
162
|
- lib/served.rb
|
134
163
|
- lib/served/attribute.rb
|
135
164
|
- lib/served/attribute/base.rb
|
165
|
+
- lib/served/backends.rb
|
166
|
+
- lib/served/backends/base.rb
|
167
|
+
- lib/served/backends/http.rb
|
168
|
+
- lib/served/backends/httparty.rb
|
169
|
+
- lib/served/backends/patron.rb
|
170
|
+
- lib/served/config.rb
|
136
171
|
- lib/served/engine.rb
|
137
172
|
- lib/served/http_client.rb
|
138
173
|
- lib/served/resource.rb
|
174
|
+
- lib/served/resource/attributable.rb
|
139
175
|
- lib/served/resource/base.rb
|
140
|
-
- lib/served/
|
141
|
-
- lib/served/
|
142
|
-
- lib/served/
|
143
|
-
- lib/served/support/validatable.rb
|
176
|
+
- lib/served/resource/configurable.rb
|
177
|
+
- lib/served/resource/serializable.rb
|
178
|
+
- lib/served/resource/validatable.rb
|
144
179
|
- lib/served/version.rb
|
145
180
|
- served.gemspec
|
146
181
|
homepage: http://github.com/fugufish/served
|
@@ -163,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
198
|
version: '0'
|
164
199
|
requirements: []
|
165
200
|
rubyforge_project:
|
166
|
-
rubygems_version: 2.6.
|
201
|
+
rubygems_version: 2.6.8
|
167
202
|
signing_key:
|
168
203
|
specification_version: 4
|
169
204
|
summary: Served provides an easy to use model layer for communicating with disributed
|
data/lib/served/support.rb
DELETED