jsonism 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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