served 0.1.12 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.org/optoro/served.svg)](https://travis-ci.org/optoro/served)
|
2
|
+
[![Build Status](https://travis-ci.org/optoro/served.svg?branch=master)](https://travis-ci.org/optoro/served)
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/served.svg)](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