json_api_resource 1.0.2 → 2.0.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: 7885048db5c04d2db691e4b6bd64ecd4c4be98bc
4
- data.tar.gz: ea2293ae4e78d3ff60cfae0a0d6a61cec82e1491
3
+ metadata.gz: 1d1601705bb9dd846cb112a656fab64ad71f8128
4
+ data.tar.gz: 01986a0b6f6af3331de1394fb40f0c92d516ad38
5
5
  SHA512:
6
- metadata.gz: 350b7f7cac7fb8ac73915c2ecb367e5cc4b8b14b1d0e68d483e982382ca896754a999b16e292429c5df53ff180870d77cb7ff5d548e493eee298827580fa23de
7
- data.tar.gz: 2a30c2ea518d8cceb37d2420ca2e5acd594968b041c2617c49763b36ca87a89e80b0e5857f98c16efb9866e96dcd84ff166fb229f64572dc1c61c234ba603163
6
+ metadata.gz: d73fc8efe1f0d87995b012f40e900220eaa4fe1261e5468f3f69bce791ea4e3c98c443e96bd7e40dae642506ddf1737124ebf34551cf0c5c60ee8658b350c7a3
7
+ data.tar.gz: 89239740324124cc32de4f453dfc48eed9115a6958c89d156743d110856379f185a17ac51ffaa594541ba75d9d40ac41930cb82e05c168b40e9e428ff1c6e3c7
data/README.md CHANGED
@@ -1,17 +1,167 @@
1
1
  # JsonApiResource
2
2
 
3
- Common code wrapper object or Adapter class to extend a JsonApiClient::Resource
3
+ JsonApiResource is an abstraction layer that sits on top of [JsonApiClient](https://github.com/chingor13/json_api_client) that lets the user interact with client objects the way they would with an ActiveRecord model. It provides objectification of attributes, population with defaults, metadata and error handling on server requests.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'json_api_resource'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install json_api_resource
4
20
 
5
21
  ## Usage
6
22
 
7
- ### Basic
23
+ ### Example
24
+
25
+ Let's say you have a JsonApi server Account, which has a RESTful controller, `UsersController` and you have a `JsonApiClient` that can talk to it. And you would like to interact with the server result as an object, rather than
8
26
 
27
+ ```ruby
28
+ user = Account::Client::User.find(15)
29
+ user["name"]
9
30
  ```
10
- class Customer < JsonApiResource::Resource
11
- property :name => 'name', :email => '', :permissions => []
12
- api_client Ledger::Client::Customer
31
+
32
+ You can wrap it in a `JsonApiResource`
33
+
34
+ ```ruby
35
+ module Account
36
+ class User < JsonApiResource::Resource
37
+
38
+ # define what your client is
39
+ wraps Account::Client::User
40
+
41
+ # define what fields you expect, with defaults, should you create a new object
42
+ # or should the server omit fields in its response
43
+ properties id: nil,
44
+ name: "",
45
+ permissions: [],
46
+ friend_ids: []
47
+
48
+ # your custom code here
49
+ def friends
50
+ @friends ||= where(id: friend_ids)
51
+ end
52
+
53
+ #etc
54
+ end
13
55
  end
56
+ ```
57
+
58
+ Then you can interface with the `Account::User` class, as you would any old AR class.
59
+
60
+ ```ruby
61
+
62
+ # find works the way you would expect
63
+ john = Account::User.find(38)
64
+ john.name = "Johnny"
65
+
66
+ # will make a PUT call to the server
67
+ john.save # => true
68
+
69
+ # if you preforma an action that fails validation
70
+ john.name = ""
71
+ john.save # => false
72
+
73
+ john.errors # ActiveModel::Errors errors that you can nicely pipe into your forms
74
+
75
+ mark = Account::User.new
76
+ mark.id # => nil
77
+
78
+ mark.name = "Mark"
79
+
80
+ mark.save # => true
81
+
82
+ # etc
83
+ ```
84
+
85
+ ### Keywords
86
+
87
+ #### wraps
88
+
89
+ `wraps` is an interface method that defines what `JsonApiClient` class the resource will wrap and connect to.
90
+
91
+ ```ruby
92
+ module Account
93
+ class User < JsonApiResource::Resource
94
+
95
+ wraps Account::Client::User
96
+
97
+ end
98
+ end
99
+ ```
100
+
101
+ #### property
102
+
103
+ Define a single property that will be populated on new object or if the field is missing.
104
+
105
+ ```ruby
106
+ class User < JsonApiResource::Resource
107
+ wraps Whatever
108
+
109
+ # defaults to nil
110
+ property :id
111
+
112
+ # defaults to ""
113
+ property :name, ""
114
+ end
115
+
116
+ ```
117
+
118
+ #### properties
119
+
120
+ Define multiple properties
121
+
122
+ ```ruby
123
+ class User < JsonApiResource::Resource
124
+ wraps Whatever
125
+
126
+ properties id: nil,
127
+ name: ""
128
+ end
129
+ ```
130
+
131
+ NOTE: all properties are optional. If you don't have it defined and it's in the payload, it will still be part of your resulting object. For example, if you define no properties, but your payload comes in as
132
+
133
+ ```json
134
+ {
135
+ "id": 10,
136
+ "name": "john",
137
+ "permissions": []
138
+ }
139
+ ```
140
+
141
+ The object will still reply to `id`, `name` and `permissions`, but you won't be able to assume they are there, and they will not appear when you do `ResourceClass.new`.
142
+
143
+ ### Other Features
144
+
145
+ #### Caching
146
+
147
+ JsonApiResource does a full digest of the json blob, so if your server schema changes, you will see your client side cache burst.
148
+
149
+ #### Error handling
150
+
151
+ As seen in the example above, JsonApiResource extracts the server errors from the metadata and wraps them nicely in `ActiveModel::Errors`, for easy form integration.
152
+
153
+
154
+ ## Development
155
+
156
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
157
+
158
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
159
+
160
+ ## Contributing
161
+
162
+ Bug reports and pull requests are welcome on GitHub at https://github.com/avvo/json_api_resource.
163
+
14
164
 
15
- item = Customer.new
16
- #<Customer:0x007f84b7a72568 @client=#<Ledger::Client::Customer:0x007f84b7a71398 @attributes={"id"=>nil, "name"=>"name", "email"=>"", "permissions"=>[]}>>
165
+ ## License
17
166
 
167
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -1,8 +1,13 @@
1
+ require 'multiconnect'
2
+
1
3
  module JsonApiResource
2
4
  module Cacheable
3
5
  extend ActiveSupport::Concern
4
- def cache_key
5
- @cache_key ||= Digest::SHA256.hexdigest(self.to_json)
6
+
7
+ included do
8
+ def cache_key
9
+ @cache_key ||= Digest::SHA256.hexdigest(self.to_json)
10
+ end
6
11
  end
7
12
  end
8
- end
13
+ end
@@ -1,3 +1,5 @@
1
+ require 'multiconnect'
2
+
1
3
  module JsonApiResource
2
4
  module Clientable
3
5
  extend ActiveSupport::Concern
@@ -5,10 +7,14 @@ module JsonApiResource
5
7
  included do
6
8
  class_attribute :client_class
7
9
  self.client_class = nil
10
+ include Multiconnect::Connectable
8
11
 
9
12
  class << self
10
13
  def wraps(client)
11
14
  self.client_class = client
15
+
16
+ # now that we know where to connect to, let's do it
17
+ add_connection Connections::ServerConnection, client: client
12
18
  end
13
19
  end
14
20
  end
@@ -0,0 +1,34 @@
1
+ module JsonApiResource
2
+ module Connections
3
+ class ServerConnection < Multiconnect::Connection::Base
4
+ class_attribute :error_notifier
5
+
6
+ def report_error( e )
7
+ error_notifier.notify( self, e ) if error_notifier.present?
8
+ end
9
+
10
+ def request( action, *args )
11
+
12
+ result = self.client.send action, *args
13
+
14
+ if result.is_a? JsonApiClient::Scope
15
+ result = result.all
16
+ end
17
+
18
+ result
19
+
20
+ rescue JsonApiClient::Errors::NotFound => e
21
+
22
+ result = JsonApiClient::ResultSet.new
23
+
24
+ result.meta = {status: 404}
25
+
26
+ result.errors = ActiveModel::Errors.new(result)
27
+ result.errors.add("RecordNotFound", e.message)
28
+
29
+ result
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module JsonApiResource
2
+ module Connections
3
+ autoload :ServerConnection, 'json_api_resource/connections/server_connection'
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module JsonApiResource
2
+ module ErrorNotifier
3
+ class Base
4
+ class << self
5
+ def notify( connection, error )
6
+ raise NotImplementedError
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module JsonApiResource
2
+ module ErrorNotifier
3
+ autoload :Base, 'json_api_resource/error_notifier/base'
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ require 'multiconnect'
2
+
3
+ module JsonApiResource
4
+ module Executable
5
+ extend ActiveModel::Callbacks
6
+ extend ActiveSupport::Concern
7
+ extend ActiveSupport::Callbacks
8
+
9
+ included do
10
+
11
+ include Multiconnect::Connectable
12
+
13
+ def execute(action, *args)
14
+ result = nil
15
+ run_callbacks action do
16
+ result = connection.execute( action, *args )
17
+ end
18
+ result.success?
19
+ end
20
+
21
+ class << self
22
+ def execute(action, *args)
23
+ result = request(action, *args)
24
+
25
+ result.map! do |result|
26
+ new(client: result)
27
+ end
28
+ rescue Multiconnect::Error::UnsuccessfulRequest => e
29
+
30
+ result = JsonApiClient::ResultSet.new
31
+ result.meta = {status: 500}
32
+
33
+ result.errors = ActiveModel::Errors.new(result)
34
+ result.errors.add("ServerError", "Unable to connect to server or server returned 500")
35
+
36
+ result
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def connection
43
+ @connection ||= Connections::ServerConnection.new( client: self.client, caching: false )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -6,8 +6,8 @@ module JsonApiResource
6
6
 
7
7
  define_model_callbacks :save, :update_attributes
8
8
 
9
- around_save :catch_errors
10
- around_update_attributes :catch_errors
9
+ around_save :update_meta
10
+ around_update_attributes :update_meta
11
11
 
12
12
  class << self
13
13
 
@@ -16,26 +16,26 @@ module JsonApiResource
16
16
  def find(id)
17
17
  return nil unless id.present?
18
18
 
19
- results = request(:find, id: id)
19
+ results = execute(:find, id: id)
20
20
  JsonApiResource::Handlers::FindHandler.new(results).result # <= <#JsonApiclient::ResultSet @errors => <...>, @data => <...>, @linked_data => <...>>
21
21
  end
22
22
 
23
23
  def where(opts = {})
24
24
  opts[:per_page] = opts.fetch(:per_page, self.per_page)
25
- request(:where, opts)
25
+ execute(:where, opts)
26
26
  end
27
27
  end
28
28
 
29
29
 
30
30
  def save
31
- request :save
31
+ execute :save
32
32
  end
33
33
 
34
34
  def update_attributes(attrs = {})
35
- request :update_attributes, attrs
35
+ execute :update_attributes, attrs
36
36
  end
37
37
 
38
- def catch_errors
38
+ def update_meta
39
39
  yield
40
40
 
41
41
  self.errors ||= ActiveModel::Errors.new(self)
@@ -43,6 +43,8 @@ module JsonApiResource
43
43
  self.errors.add(k.to_sym, Array(messages).join(', '))
44
44
  end
45
45
  self.errors
46
+
47
+ self.meta = self.client.last_request_meta
46
48
  end
47
49
  end
48
50
  end
@@ -19,7 +19,7 @@ module JsonApiResource
19
19
  include JsonApiResource::Clientable
20
20
  include JsonApiResource::Schemable
21
21
  include JsonApiResource::Queryable
22
- include JsonApiResource::Requestable
22
+ include JsonApiResource::Executable
23
23
  include JsonApiResource::Conversions
24
24
  include JsonApiResource::Cacheable
25
25
 
@@ -34,6 +34,7 @@ module JsonApiResource
34
34
  self.client = self.client_class.new(self.schema)
35
35
  self.errors = ActiveModel::Errors.new(self)
36
36
  self.attributes = opts
37
+ self.populate_missing_fields
37
38
  end
38
39
 
39
40
  def new_record?
@@ -61,15 +62,10 @@ module JsonApiResource
61
62
  if match = method.to_s.match(/^(.*=)$/)
62
63
  self.client.send(match[0], args.first)
63
64
  elsif self.client.respond_to?(method.to_sym)
64
- self.client.send(method, *args)
65
+ connection.execute( method, *args ).data
65
66
  else
66
67
  super
67
68
  end
68
-
69
- rescue JsonApiClient::Errors::ServerError => e
70
- add_error e
71
- rescue ArgumentError => e
72
- raise JsonApiResourceError, class: self.class, message: "#{method}: #{e.message}"
73
69
  end
74
70
 
75
71
  def self.method_missing(method, *args, &block)
@@ -77,7 +73,7 @@ module JsonApiResource
77
73
  self.client_class.send(match[0], args.first)
78
74
 
79
75
  elsif self.client_class.respond_to?(method.to_sym)
80
- results = self.client_class.send(method, *args)
76
+ results = request(method, *args).data
81
77
 
82
78
  if results.is_a? JsonApiClient::ResultSet
83
79
  results.map! do |result|
@@ -86,14 +82,11 @@ module JsonApiResource
86
82
  end
87
83
 
88
84
  results
89
-
90
85
  else
91
86
  super
92
87
  end
93
- rescue JsonApiClient::Errors::ServerError => e
94
- empty_set_with_errors e
95
- rescue ArgumentError => e
96
- raise JsonApiResourceError, class: self, message: "#{method}: #{e.message}"
88
+ rescue Multiconnect::Error::UnsuccessfulRequest => e
89
+ nil
97
90
  end
98
91
 
99
92
  def respond_to_missing?(method_name, include_private = false)
@@ -19,6 +19,16 @@ module JsonApiResource
19
19
  self.schema = schema.merge name.to_sym => default
20
20
  end
21
21
  end
22
+
23
+ protected
24
+
25
+ def populate_missing_fields
26
+ self.class.schema.each_pair do |key, value|
27
+ unless self.attributes.has_key?(key)
28
+ self.attributes[key] = value
29
+ end
30
+ end
31
+ end
22
32
  end
23
33
  end
24
34
  end
@@ -1,3 +1,3 @@
1
1
  module JsonApiResource
2
- VERSION = "1.0.2"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -3,11 +3,13 @@ require 'json_api_client'
3
3
  module JsonApiResource
4
4
  autoload :Cacheable, 'json_api_resource/cacheable'
5
5
  autoload :Clientable, 'json_api_resource/clientable'
6
+ autoload :Connections, 'json_api_resource/connections'
6
7
  autoload :Conversions, 'json_api_resource/conversions'
8
+ autoload :ErrorNotifier, 'json_api_resource/error_notifier'
7
9
  autoload :Handlers, 'json_api_resource/handlers'
8
10
  autoload :JsonApiResourceError, 'json_api_resource/json_api_resource_error'
9
11
  autoload :Queryable, 'json_api_resource/queryable'
10
12
  autoload :Resource, 'json_api_resource/resource'
11
- autoload :Requestable, 'json_api_resource/requestable'
13
+ autoload :Executable, 'json_api_resource/executable'
12
14
  autoload :Schemable, 'json_api_resource/schemable'
13
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_api_resource
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Sislow
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-28 00:00:00.000000000 Z
11
+ date: 2016-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json_api_client
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: multiconnect
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: webmock
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -92,12 +106,16 @@ files:
92
106
  - lib/json_api_resource.rb
93
107
  - lib/json_api_resource/cacheable.rb
94
108
  - lib/json_api_resource/clientable.rb
109
+ - lib/json_api_resource/connections.rb
110
+ - lib/json_api_resource/connections/server_connection.rb
95
111
  - lib/json_api_resource/conversions.rb
112
+ - lib/json_api_resource/error_notifier.rb
113
+ - lib/json_api_resource/error_notifier/base.rb
114
+ - lib/json_api_resource/executable.rb
96
115
  - lib/json_api_resource/handlers.rb
97
116
  - lib/json_api_resource/handlers/find_handler.rb
98
117
  - lib/json_api_resource/json_api_resource_error.rb
99
118
  - lib/json_api_resource/queryable.rb
100
- - lib/json_api_resource/requestable.rb
101
119
  - lib/json_api_resource/resource.rb
102
120
  - lib/json_api_resource/schemable.rb
103
121
  - lib/json_api_resource/version.rb
@@ -121,9 +139,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
139
  version: '0'
122
140
  requirements: []
123
141
  rubyforge_project:
124
- rubygems_version: 2.4.5
142
+ rubygems_version: 2.4.6
125
143
  signing_key:
126
144
  specification_version: 4
127
145
  summary: Build wrapper/adapter objects around JsonApiClient instances
128
146
  test_files: []
129
- has_rdoc:
@@ -1,71 +0,0 @@
1
- module JsonApiResource
2
- module Requestable
3
- extend ActiveModel::Callbacks
4
- extend ActiveSupport::Concern
5
- extend ActiveSupport::Callbacks
6
-
7
- included do
8
- def request(action, *args)
9
- run_callbacks action do
10
- self.client.send(action, *args)
11
- end
12
- self
13
-
14
- rescue JsonApiClient::Errors::ServerError => e
15
- add_error e
16
- end
17
-
18
- class << self
19
- def request(action, *args)
20
- result = self.client_class.send(action, *args)
21
-
22
- result = result.all if result.is_a? JsonApiClient::Scope
23
-
24
- result.map! do |result|
25
- new(client: result)
26
- end
27
-
28
- rescue JsonApiClient::Errors::ServerError => e
29
- empty_set_with_errors e
30
- end
31
-
32
- def empty_set_with_errors(e)
33
- append_errors e do |status, error|
34
- error_response status, error
35
- end
36
- end
37
-
38
- def append_errors(e, &block)
39
- case e.class.to_s
40
-
41
- when "JsonApiClient::Errors::NotFound"
42
- yield 404, { name: "RecordNotFound", message: e.message }
43
-
44
- when "JsonApiClient::Errors::UnexpectedStatus"
45
- yield e.code, { name: "UnexpectedStatus", message: e.message }
46
-
47
- else
48
- yield 500, { name: "ServerError", message: e.message }
49
- end
50
- end
51
-
52
- def error_response(status, error)
53
- result = JsonApiClient::ResultSet.new
54
-
55
- result.meta = {status: status}
56
-
57
- result.errors = ActiveModel::Errors.new(result)
58
- result.errors.add(error[:name], Array(error[:message]).join(', '))
59
-
60
- result
61
- end
62
- end
63
-
64
- def add_error(e, &block)
65
- self.class.append_errors e do |status, error|
66
- errors.add error[:name], error[:message]
67
- end
68
- end
69
- end
70
- end
71
- end