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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6caeaa76f12084ca80555e8bf472e9f85ebcae70
4
- data.tar.gz: 006b93ac7370979237e29fffe4b49a70938684fe
3
+ metadata.gz: 20a3772be5d8a3b6c27909c4cb61c4bdd33fdb2c
4
+ data.tar.gz: 3bc802236bc9a3f77bdab3577bb917313fc29332
5
5
  SHA512:
6
- metadata.gz: 9bc45c23b36761e2c2a24bb7fc295f75a807969455c02347acb74d3664de0eb865716b16da51e401bbe73a50fc1ab60fa6c898ec5e816c800d02092425770697
7
- data.tar.gz: 51e0f9ceda390a79ee3da2b1fe071ca376bf3538506d7ce6092e327e51b313200a354521b697c7b8034554c5de04db5a7b01aa33f430006a7e16dccdf52a4abc
6
+ metadata.gz: 4b9c306b2e26bd19e0fdfef7ce4abd9c14172c2490030710613c44041787e3ae07ff8dd47d51975163eefa405dd0249cd43861bb115d8df3a9ba1471517b5973
7
+ data.tar.gz: 3f45537c4946005cf8e056db0d9832e95821ba21ce8c47c2edea36582c878eeb2bf186e56b0aff7fcbd535bb6fec79dbfd8bac04a0688140193a3a24487c5aae
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.0
data/.travis.yml CHANGED
@@ -2,7 +2,8 @@ language: ruby
2
2
  rvm:
3
3
  - 2.1
4
4
  - 2.2.2
5
- - 2.3.1
5
+ - 2.3.3
6
+ - 2.4.0
6
7
  before_install: gem install bundler -v 1.10.5
7
8
 
8
9
  gemfile:
@@ -13,4 +14,4 @@ gemfile:
13
14
  matrix:
14
15
  exclude:
15
16
  - gemfile: gemfiles/5.0.gemfile
16
- rvm: 2.1
17
+ rvm: 2.1
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
- * `host_config` will be deprecated in 0.2.0
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 Support::Attributable
5
- include Support::Serializable
6
- include Support::Validatable
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,16 @@
1
+ module Served
2
+ module Backends
3
+ class Base
4
+ delegate :headers, :resource, :template, :timeout, to: :@client
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def serialize_response(response)
11
+ response
12
+ end
13
+
14
+ end
15
+ end
16
+ 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
@@ -0,0 +1,12 @@
1
+ module Served
2
+ include ActiveSupport::Configurable
3
+ config_accessor :timeout
4
+ config_accessor :backend
5
+
6
+ configure do |config|
7
+ config.timeout = 30
8
+ config.backend = :http
9
+ config.hosts = {}
10
+ end
11
+
12
+ end
@@ -1,38 +1,29 @@
1
1
  require 'addressable/template'
2
2
  module Served
3
- # Provides an interface between HTTParty and the models. Most of the crap in here is self explanatory
3
+ # Provides an interface between the HTTP client and the Resource.
4
4
  class HTTPClient
5
- HEADERS = { 'Content-type' => 'application/json', 'Accept' => 'application/json' }
5
+
6
6
  DEFAULT_TEMPLATE = '{/resource*}{/id}.json{?query*}'
7
7
 
8
- def initialize(host, timeout, headers={})
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
- def get(endpoint, id, params={})
16
- HTTParty.get(@template.expand(id: id, query: params, resource: endpoint).to_s,
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
- def put(endpoint, id, body, params={})
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
- def post(endpoint, body, params={})
31
- HTTParty.post(@template.expand(query: params, resource: endpoint).to_s,
32
- body: body,
33
- headers: @headers,
34
- timeout: @timeout
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 Support
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
- def initialize(options={})
39
- reload_with_attributes(options)
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
@@ -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 Support::Attributable
16
- include Support::Validatable
17
- include Support::Serializable
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
- super "An error occurred making the request: #{@response.code}"
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
- class << self
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
- @headers = h unless h.empty?
42
- @headers
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
- # Get or set the resource name for the given resource used for endpoint generation
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
- @connection ||= Served::HTTPClient.new(host_config, timeout, headers)
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
- # @see Services::Resource::Base::resource_name
104
- def resource_name
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 Support
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
- Float => {call: :to_f}
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
- value = value.send(s[:call]) if s[:call] && value.respond_to?(s[:call])
31
- value = s[:converter].call(value) if s[:converter]
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
- {resource_name.singularize => attributes}
79
+ attributes
62
80
  end
63
81
 
64
82
  end
@@ -1,5 +1,5 @@
1
1
  module Served
2
- module Support
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 unless valid?
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
@@ -1 +1 @@
1
- require 'served/resource/base'
1
+ require 'served/resource/base'
@@ -1,3 +1,3 @@
1
1
  module Served
2
- VERSION = '0.1.12'
2
+ VERSION = '0.2.0'
3
3
  end
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
- module Served
15
- include ActiveSupport::Configurable
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 'bundler', '~> 1.10'
28
- spec.add_development_dependency 'rake', '~> 10.0'
29
- spec.add_development_dependency 'rspec'
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.1.12
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-02-06 00:00:00.000000000 Z
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: '0'
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: '0'
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/support.rb
141
- - lib/served/support/attributable.rb
142
- - lib/served/support/serializable.rb
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.10
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
@@ -1,3 +0,0 @@
1
- require_relative 'support/attributable'
2
- require_relative 'support/serializable'
3
- require_relative 'support/validatable'