jsonism 0.0.1 → 0.0.2

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: b5974d3d075706ced2dba0cbf08836c273631104
4
- data.tar.gz: 904c1343e967ecb95335eaba195a6f11e4cae904
3
+ metadata.gz: 7e52cd62c6c50669db16bc69bc716d183d6e5ef6
4
+ data.tar.gz: 85895e83b5b2a28d6ff44e639a18a22f6e8d2d6c
5
5
  SHA512:
6
- metadata.gz: ff268a39733452a783f162a646e9772fc8351bd3221b0de026837955b1f3e1d017564b43220e40db11337888f6ae8d6fbc6699fcf8c74180dec12f314527ab05
7
- data.tar.gz: e75a3f4eb5072fac3ab3e18b52f7b2d2df28a3aa833c5acfec5292f5935fa5d8273253483d11dcde331e584991475d9acd736d079b9e0f19dbdf113bc7ff69d1
6
+ metadata.gz: a8151d1bfd6ce0f815ab3e1980c7645c93d48dd41d02b07976785eace90beab9baa0639d70ec387224eef39959f29296ca95e9cb49baecd43638ef43f55423f9
7
+ data.tar.gz: 30dbc50cd609e3ccd6f15ae81d580eba75688734a2841cca93b1a64d02d9aff0c2a51cf007dbf650906b09d84161eaf8519c959f8e6611415f82cde903b06f57
@@ -0,0 +1,5 @@
1
+ ## 0.0.2
2
+ * Define resource.delete & resource.update methods if correspondent links are defined
3
+
4
+ ## 0.0.1
5
+ * 1st release
data/README.md CHANGED
@@ -20,7 +20,7 @@ client.info_app(id: 1)
20
20
  # POST /apps
21
21
  client.create_app(name: "alpha")
22
22
 
23
- # PUT /apps/1
23
+ # PATCH /apps/1
24
24
  client.update_app(id: 1, name: "bravo")
25
25
 
26
26
  # DELETE /apps/1
@@ -28,6 +28,35 @@ client.delete_app(id: 1)
28
28
 
29
29
  # GET /recipes
30
30
  client.list_recipe
31
+
32
+ # Each method returns a Jsonism::Response
33
+ client.list_app.class #=> Jsonism::Response
34
+
35
+ # Jsonism::Response has 3 methods: .status, .headers, .body
36
+ client.list_app.status #=> 200
37
+ client.list_app.headers["Content-Type"] #=> "application/json"
38
+ client.list_app.body #=> [#<Jsonism::Resources::App:0x007f871ec96240>]
39
+
40
+ # Jsonism::Response#body returns an instance of auto-defined class, or ones wrapped in Array
41
+ # This class name is assigned from its title property on JSON Schema
42
+ client.list_app.body[0].class #=> Jsonism::Resources::App
43
+ client.info_app(id: 1).body.class #=> Jsonism::Resources::App
44
+
45
+ # Auto-defined resource class inherits from Jsonism::Resources::Base
46
+ client.list_app.body[0].class.ancestors[1] #=> Jsonism::Resources::Base
47
+
48
+ # Jsonism::Resources::Base has a method: .to_hash
49
+ client.list_app.body[0].to_hash #=> {"id"=>"01234567-89ab-cdef-0123-456789abcdef", "name"=>"example"}
50
+
51
+ # Resource can respond to .delete method if a link with rel=delete is defined in schema
52
+ # DELETE /apps/1
53
+ client.info_app.body[0].body.delete
54
+
55
+ # Resource can also respond to .update method in the same rule
56
+ # PATCH /apps/1
57
+ resource = client.info_app.body[0]
58
+ resource.name = "charlie"
59
+ resource.update
31
60
  ```
32
61
 
33
62
  ## Errors
@@ -21,6 +21,10 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency "faraday_middleware"
22
22
  spec.add_dependency "json_schema"
23
23
  spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "pry"
24
25
  spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rack"
27
+ spec.add_development_dependency "rack-json_schema"
25
28
  spec.add_development_dependency "rspec", "2.14.1"
29
+ spec.add_development_dependency "webmock"
26
30
  end
@@ -1,3 +1,4 @@
1
+ require "active_support/concern"
1
2
  require "active_support/core_ext/hash/indifferent_access"
2
3
  require "active_support/core_ext/hash/keys"
3
4
  require "active_support/core_ext/hash/slice"
@@ -13,4 +14,8 @@ require "jsonism/client"
13
14
  require "jsonism/definer"
14
15
  require "jsonism/link"
15
16
  require "jsonism/request"
17
+ require "jsonism/resources/base"
18
+ require "jsonism/resources/deletable"
19
+ require "jsonism/resources/updatable"
20
+ require "jsonism/response"
16
21
  require "jsonism/version"
@@ -23,6 +23,13 @@ module Jsonism
23
23
  @base_url ||= root_link.try(:href) or raise BaseUrlNotFound
24
24
  end
25
25
 
26
+ # @return [Hash<String, Class>]
27
+ # @example
28
+ # resource_classes #=> { "App" #=> #<Class> }
29
+ def resource_classes
30
+ @resource_classes ||= {}
31
+ end
32
+
26
33
  private
27
34
 
28
35
  # Defines some methods into itself from its JSON Schema
@@ -22,12 +22,8 @@ module Jsonism
22
22
 
23
23
  # Defines methods to call HTTP request from its JSON schema
24
24
  def call
25
- client = @client
26
- links.each do |link|
27
- @client.define_singleton_method(link.method_signature) do |params = {}, headers = {}|
28
- Request.call(client: client, headers: headers, link: link, params: params)
29
- end
30
- end
25
+ define_methods_into(@client)
26
+ define_classes
31
27
  end
32
28
 
33
29
  private
@@ -38,5 +34,54 @@ module Jsonism
38
34
  Link.new(link: link)
39
35
  end
40
36
  end
37
+
38
+ def define_classes
39
+ @schema.properties.each do |name, schema|
40
+ unless Resources.const_defined?(name.camelize)
41
+ Resources.const_set(
42
+ name.camelize,
43
+ Class.new(Resources::Base) do
44
+ schema.properties.each do |name, schema|
45
+ define_method(name) do
46
+ @properties[name]
47
+ end
48
+
49
+ if schema.read_only
50
+ read_only_property name
51
+ else
52
+ define_method("#{name}=") do |value|
53
+ change(name, value) if @properties[name] != value
54
+ end
55
+ end
56
+ end
57
+
58
+ schema.links.each do |link|
59
+ if link.rel == "delete"
60
+ include Resources::Deletable
61
+ self.link_for_deletion = Link.new(link: link)
62
+ end
63
+
64
+ if link.rel == "update"
65
+ include Resources::Updatable
66
+ self.link_for_update = Link.new(link: link)
67
+ end
68
+ end
69
+ end
70
+ )
71
+ end
72
+ end
73
+ end
74
+
75
+ # Defines methods into client
76
+ # @example
77
+ # client.list_app
78
+ # client.info_app(id: 1)
79
+ def define_methods_into(client)
80
+ links.each do |link|
81
+ @client.define_singleton_method(link.method_signature) do |params = {}, headers = {}|
82
+ Request.call(client: client, headers: headers, link: link, params: params)
83
+ end
84
+ end
85
+ end
41
86
  end
42
87
  end
@@ -26,12 +26,12 @@ module Jsonism
26
26
  @link.href
27
27
  end
28
28
 
29
- private
30
-
31
29
  def schema_title
32
30
  schema.title
33
31
  end
34
32
 
33
+ private
34
+
35
35
  def link_title
36
36
  @link.title
37
37
  end
@@ -9,17 +9,23 @@ module Jsonism
9
9
  # @param headers [Hash]
10
10
  # @param link [Jsonism::Link]
11
11
  # @param params [Hash]
12
- def initialize(client: nil, headers: {}, link: nil, params: {})
12
+ # @param options [Hash] Options to change the default behavior
13
+ def initialize(client: nil, headers: {}, link: nil, params: {}, **options)
13
14
  @client = client
14
15
  @headers = headers
15
16
  @link = link
16
17
  @params = params.with_indifferent_access
18
+ @options = options
17
19
  end
18
20
 
19
21
  # Sends HTTP request
20
22
  def call
21
23
  if has_valid_params?
22
- @client.connection.send(method, path)
24
+ Response.new(
25
+ client: @client,
26
+ resource_class: resource_class,
27
+ response: @client.connection.send(method, path),
28
+ )
23
29
  else
24
30
  raise MissingParams, missing_params
25
31
  end
@@ -27,6 +33,15 @@ module Jsonism
27
33
 
28
34
  private
29
35
 
36
+ # @return [Class] Auto-defined resource class
37
+ def resource_class
38
+ Resources.const_get(resource_class_name)
39
+ end
40
+
41
+ def resource_class_name
42
+ @link.schema_title.camelize
43
+ end
44
+
30
45
  # @return [true, false] False if any keys in path template are missing
31
46
  def has_valid_params?
32
47
  missing_params.empty?
@@ -37,6 +52,13 @@ module Jsonism
37
52
  @missing_params ||= path_keys - @params.keys
38
53
  end
39
54
 
55
+ # @return [Array<String>] Parameter names marked as readonly
56
+ def read_only_params
57
+ @read_only_params ||= @link.schema.properties.map do |name, property|
58
+ name if property.read_only
59
+ end.compact
60
+ end
61
+
40
62
  # @return [String] Method name to call connection's methods
41
63
  # @example
42
64
  # method #=> "get"
@@ -81,7 +103,13 @@ module Jsonism
81
103
  # @example
82
104
  # request_params #=> { name: "example" }
83
105
  def request_params
84
- @params.except(*path_keys)
106
+ unless configured_to_ignore_request_params?
107
+ @params.except(*path_keys, *read_only_params)
108
+ end
109
+ end
110
+
111
+ def configured_to_ignore_request_params?
112
+ p !!@options[:ignore_request_params]
85
113
  end
86
114
 
87
115
  class MissingParams < Error
@@ -0,0 +1,52 @@
1
+ module Jsonism
2
+ module Resources
3
+ class Base
4
+ class << self
5
+ def read_only_property(name)
6
+ read_only_properties << name
7
+ end
8
+
9
+ def read_only_properties
10
+ @read_only_properties ||= []
11
+ end
12
+ end
13
+
14
+ # @param client [Jsonism::Client]
15
+ # @param properties [Hash]
16
+ def initialize(client: nil, properties: nil)
17
+ @client = client
18
+ @properties = properties
19
+ end
20
+
21
+ def to_hash
22
+ @properties.clone
23
+ end
24
+
25
+ def changed?
26
+ !changed_properties.empty?
27
+ end
28
+
29
+ def save
30
+ @previously_changed_properties = changed_properties
31
+ @changed_properties = {}
32
+ end
33
+
34
+ def change(name, value)
35
+ changed_properties[name] = value
36
+ @properties[name] = value
37
+ end
38
+
39
+ def changed_properties
40
+ @changed_properties ||= {}
41
+ end
42
+
43
+ def previously_changed_properties
44
+ @previously_changed_properties ||= {}
45
+ end
46
+
47
+ def read_only_properties
48
+ to_hash.slice(*self.class.read_only_properties)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,21 @@
1
+ module Jsonism
2
+ module Resources
3
+ module Deletable
4
+ extend ActiveSupport::Concern
5
+
6
+ def delete(params = {}, headers = {})
7
+ Request.call(
8
+ client: @client,
9
+ headers: headers,
10
+ link: self.class.link_for_deletion,
11
+ params: to_hash,
12
+ ignore_request_params: true,
13
+ )
14
+ end
15
+
16
+ module ClassMethods
17
+ attr_accessor :link_for_deletion
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module Jsonism
2
+ module Resources
3
+ module Updatable
4
+ extend ActiveSupport::Concern
5
+
6
+ def update(params = {}, headers = {})
7
+ Request.call(
8
+ client: @client,
9
+ headers: headers,
10
+ link: self.class.link_for_update,
11
+ params: read_only_properties.merge(changed_properties.merge(params)),
12
+ )
13
+ end
14
+
15
+ module ClassMethods
16
+ attr_accessor :link_for_update
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ module Jsonism
2
+ class Response
3
+ # @param client [Jsonism::Client]
4
+ # @param resource_class [Class]
5
+ # @param response [Faraday::Response]
6
+ def initialize(client: nil, resource_class: nil, response: nil)
7
+ @client = client
8
+ @resource_class = resource_class
9
+ @response = response
10
+ end
11
+
12
+ def body
13
+ if has_list?
14
+ @response.body.map do |properties|
15
+ @resource_class.new(client: @client, properties: properties)
16
+ end
17
+ else
18
+ @resource_class.new(client: @client, properties: @response.body)
19
+ end
20
+ end
21
+
22
+ def headers
23
+ @response.headers
24
+ end
25
+
26
+ def status
27
+ @response.status
28
+ end
29
+
30
+ private
31
+
32
+ def has_list?
33
+ Array === @response.body
34
+ end
35
+
36
+ def raw_body
37
+ @response.body
38
+ end
39
+ end
40
+ end
@@ -1,3 +1,3 @@
1
1
  module Jsonism
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -52,7 +52,7 @@
52
52
  "description": "Delete an existing app.",
53
53
  "href": "/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fid)}",
54
54
  "method": "DELETE",
55
- "rel": "destroy",
55
+ "rel": "delete",
56
56
  "title": "Delete"
57
57
  },
58
58
  {
@@ -1,6 +1,18 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Jsonism::Client do
4
+ before do
5
+ stub_request(:any, //).to_rack(mock_app)
6
+ end
7
+
8
+ let(:mock_app) do
9
+ local_schema = schema
10
+ Rack::Builder.new do
11
+ use Rack::JsonSchema::Mock, schema: local_schema
12
+ run ->(env) { [404, {}, ["Not Found"]] }
13
+ end
14
+ end
15
+
4
16
  let(:instance) do
5
17
  described_class.new(schema: schema)
6
18
  end
@@ -17,6 +29,10 @@ describe Jsonism::Client do
17
29
  File.expand_path("../../fixtures/schema.json", __FILE__)
18
30
  end
19
31
 
32
+ let(:params) do
33
+ { id: 1 }
34
+ end
35
+
20
36
  describe ".new" do
21
37
  subject do
22
38
  instance
@@ -41,9 +57,16 @@ describe Jsonism::Client do
41
57
  instance.list_app
42
58
  end
43
59
 
44
- it "sends HTTP request to GET /apps" do
45
- instance.connection.should_receive(:get).with("/apps")
46
- subject
60
+ context "with valid condition" do
61
+ it "sends HTTP request to GET /apps" do
62
+ instance.connection.should_receive(:get).with("/apps")
63
+ subject
64
+ end
65
+
66
+ it "returns an Array of resources" do
67
+ subject.body.should be_a Array
68
+ subject.body[0].should be_a Jsonism::Resources::App
69
+ end
47
70
  end
48
71
  end
49
72
 
@@ -52,15 +75,21 @@ describe Jsonism::Client do
52
75
  instance.info_app(params)
53
76
  end
54
77
 
55
- let(:params) do
56
- { id: 1 }
57
- end
58
-
59
78
  context "with valid condition" do
60
79
  it "sends HTTP request to GET /apps/:id" do
61
80
  instance.connection.should_receive(:get).with("/apps/1")
62
81
  subject
63
82
  end
83
+
84
+ it "returns a Jsonism::Response" do
85
+ should be_a Jsonism::Response
86
+ end
87
+
88
+ it "returns status, headers, and body data" do
89
+ subject.status.should == 200
90
+ subject.headers.should be_a Hash
91
+ subject.body.should be_a Jsonism::Resources::App
92
+ end
64
93
  end
65
94
 
66
95
  context "with missing params" do
@@ -73,4 +102,45 @@ describe Jsonism::Client do
73
102
  end
74
103
  end
75
104
  end
105
+
106
+ describe "#delete_app" do
107
+ let(:resource) do
108
+ instance.info_app(params).body
109
+ end
110
+
111
+ context "with valid condition" do
112
+ it "sends HTTP request to DELETE /apps/:id" do
113
+ instance.connection.should_receive(:delete).with("/apps/1")
114
+ instance.delete_app(params)
115
+ end
116
+ end
117
+
118
+ context "via app.delete" do
119
+ it "can be called from app.delete too" do
120
+ instance.connection.should_receive(:delete).with("/apps/#{resource.id}")
121
+ resource.delete
122
+ end
123
+ end
124
+ end
125
+
126
+ describe "#update_app" do
127
+ let(:resource) do
128
+ instance.info_app(params).body
129
+ end
130
+
131
+ context "with valid condition" do
132
+ it "sends HTTP request to PATCH /apps/:id" do
133
+ instance.connection.should_receive(:patch).with("/apps/1")
134
+ instance.update_app(params)
135
+ end
136
+ end
137
+
138
+ context "via app.update" do
139
+ it "can be called from app.update too" do
140
+ instance.connection.should_receive(:patch).with("/apps/#{resource.id}")
141
+ resource.name = "bravo"
142
+ resource.update
143
+ end
144
+ end
145
+ end
76
146
  end
@@ -1,4 +1,7 @@
1
1
  require "jsonism"
2
+ require "rack"
3
+ require "rack/json_schema"
4
+ require "webmock/rspec"
2
5
 
3
6
  RSpec.configure do |config|
4
7
  config.treat_symbols_as_metadata_keys_with_true_values = true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonism
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Nakamura
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rake
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +108,34 @@ dependencies:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rack
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rack-json_schema
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
97
139
  - !ruby/object:Gem::Dependency
98
140
  name: rspec
99
141
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +150,20 @@ dependencies:
108
150
  - - '='
109
151
  - !ruby/object:Gem::Version
110
152
  version: 2.14.1
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
111
167
  description:
112
168
  email:
113
169
  - r7kamura@gmail.com
@@ -116,6 +172,7 @@ extensions: []
116
172
  extra_rdoc_files: []
117
173
  files:
118
174
  - ".gitignore"
175
+ - CHANGELOG.md
119
176
  - Gemfile
120
177
  - LICENSE.txt
121
178
  - README.md
@@ -127,6 +184,10 @@ files:
127
184
  - lib/jsonism/error.rb
128
185
  - lib/jsonism/link.rb
129
186
  - lib/jsonism/request.rb
187
+ - lib/jsonism/resources/base.rb
188
+ - lib/jsonism/resources/deletable.rb
189
+ - lib/jsonism/resources/updatable.rb
190
+ - lib/jsonism/response.rb
130
191
  - lib/jsonism/version.rb
131
192
  - spec/fixtures/schema.json
132
193
  - spec/jsonism/client_spec.rb