ruby_json_api_client 0.0.1 → 0.0.4

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: cf21703bffbc99abd22c2bef3825db7ff9f67f4c
4
- data.tar.gz: 49b8168022402b4fb3181b47826b8155f92e509d
3
+ metadata.gz: 0a316d1d7a3724fcb5dc5d292aaab71e6cda9535
4
+ data.tar.gz: 31cbe130cb74ed13c5a0eef4d75edc3f36ffc440
5
5
  SHA512:
6
- metadata.gz: 5e9219c09fe7bb70c03cd6d8587bf1000e2fe5da981e7eae5142dabded8823daf34810a686f2a8c1acac3b0c21b3ab61806e8e18f461eca998eca33d5455d991
7
- data.tar.gz: fcf2012a06ac3b1766d99ec2d56cf6717a740ac1ce41de60d31953b3dc7e708c89f320cb528a1b623811b4c40b70197fec152d689a93256395a6010aa3c354df
6
+ metadata.gz: 701fb12f1a1260d53ad736567576f15996ff1b8e7d423519be2aae8c5998ede272fa1950a05340863f8448df3487a3be863230a367a44d35f1f813acff2268e6
7
+ data.tar.gz: 89d0046c35a7477a9117e5ba4a369e901f69e0d5b7aa0cb507bc3758ef319ce63b856ec2032176f701b21418d5c5ac541d281ba5af26307d75ed0ee03ca78163
data/README.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # RubyJsonApiClient
2
2
 
3
+ A library for creating models from API endpoints.
4
+
5
+ Imagine an API endpoint that looks like this
6
+
7
+ # http://www.example.com/blogs/1
8
+ {
9
+ blog: {
10
+ id: 1,
11
+ name: "A blog",
12
+ links: {
13
+ posts: "/posts?blog_id=1"
14
+ }
15
+ }
16
+ }
17
+
18
+ RubyJsonApiClient allows you to write
19
+
20
+ class Blog < RubyJsonApiClient::Base
21
+ field :name
22
+ has_many :posts
23
+ end
24
+
25
+ Blog.find(1).name
26
+ # => "A blog"
27
+
28
+ Blog.find(1).posts.map(&:title)
29
+ # => ["Rails is Omakase"]
30
+
31
+
32
+ It has multiple API adapters so working with any serialization format is
33
+ easy.
34
+
35
+ #### Currently support formats
36
+ * Active Model Serializers
3
37
 
4
38
  ## Installation
5
39
 
@@ -13,13 +47,107 @@ And then execute:
13
47
 
14
48
  ## Usage
15
49
 
16
- TODO: Write usage instructions here
50
+ #### Setting up the adapter/serializer
51
+
52
+ The first thing that needs to be done is creating an adapter and
53
+ serializer. It is recommended you use one of the adapters that ships
54
+ with this gem.
55
+
56
+ For this example, we will setup an adapter that reads from
57
+ ActiveModelSerializers based APIs.
58
+
59
+ # config/initializers/ruby_json_api_client.rb
60
+
61
+ # Setup the AMS adapter to pull from https://www.example.com
62
+ RubyJsonApiClient::Store.register_adapter(:ams, {
63
+ hostname: 'www.example.com',
64
+ secure: true
65
+ });
66
+
67
+ # Use AMS based serializer
68
+ RubyJsonApiClient::Store.register_serializer(:ams)
69
+
70
+ # Default all models to AMS
71
+ RubyJsonApiClient::Store.default(:ams)
72
+
73
+
74
+ #### Models
75
+
76
+ Now you can setup your classes based on your API endpoints.
77
+
78
+ class Book < RubyJsonApiClient::Base
79
+ field :title
80
+ has_one :author
81
+ end
82
+
83
+ Book.all
84
+ # => Loads collection from https://www.example.com/books
85
+
86
+ Book.query(type: 'fiction')
87
+ # => Loads collection from https://www.example.com/books?type=fiction
88
+
89
+ Book.find(1)
90
+ # => Loads model from https://www.example.com/books/1
91
+
92
+ #### Relationships
93
+
94
+ Most relationships rules are defined based on the semantics of the
95
+ adapter you choose. For example, when using Active Model Serializers
96
+ relationships are mostly likely going to be sideloaded or accessed by
97
+ links.
98
+
99
+ class Book < RubyJsonApiClient::Base
100
+ field :title
101
+ has_one :author
102
+ end
103
+
104
+ class Author < RubyJsonApiClient::Base
105
+ field :name
106
+ has_many :books
107
+ end
108
+
109
+ With an AMS API that sideloads relationships, such as:
110
+
111
+ # http://www.example.com/books
112
+ {
113
+ books: [{
114
+ id: 1,
115
+ title: "Example book",
116
+ author_id: 123
117
+ }],
118
+ authors: [{
119
+ id: 123,
120
+ name: "Test author"
121
+ book_ids: [1]
122
+ }]
123
+ }
124
+
125
+ We could do the following.
126
+
127
+ Book.find(1).author.name
128
+ # => "Test author"
129
+
130
+ Author.find(123).books
131
+ # => [Book(<id: 1>)]
132
+
133
+ There are many more ways to load relationship data. You should consult
134
+ the adapter guide based on which adapter you are using.
17
135
 
18
136
  ## TODO
19
137
 
20
138
  * Per model serializers and adapters (Store#adapter_for_class)
21
139
  * Store#find_many_relationship should return reloadable proxy
140
+ * Store#find_single_relationship should return reloadable proxy
141
+ * Write AMS adapter docs
142
+ * Write docs for custom adapter
143
+ * Auto figure out default serializer from registered adapter(s)
144
+ * has one should accept field_id = 123. Post.find(1).author_id = 5
145
+ * Adapter/serializer should be able to add methods to models
146
+ (author_id=)
147
+ * Faraday follow redirectos
148
+ * Faraday cache
149
+ * REST handle 4xx errors
22
150
 
23
151
  #### AMS
24
152
 
25
- * serializer json to model rename data to model
153
+ * serializer: json to model rename data to model
@@ -1,15 +1,4 @@
1
1
  module RubyJsonApiClient
2
2
  class AmsAdapter < RubyJsonApiClient::RestAdapter
3
- def create(model)
4
-
5
- end
6
-
7
- def update(model)
8
-
9
- end
10
-
11
- def destroy(model)
12
-
13
- end
14
3
  end
15
4
  end
@@ -1,5 +1,7 @@
1
1
  require 'faraday'
2
+ require 'faraday_middleware'
2
3
  require "addressable/uri"
4
+ require 'json'
3
5
  require 'active_support'
4
6
 
5
7
  module RubyJsonApiClient
@@ -9,8 +11,14 @@ module RubyJsonApiClient
9
11
  attr_accessor :namespace
10
12
  attr_accessor :port
11
13
  attr_accessor :url_root
14
+ attr_accessor :http_client
15
+ attr_accessor :required_query_params
12
16
 
13
17
  def initialize(options = {})
18
+ if options[:http_client].nil?
19
+ options[:http_client] = :net_http
20
+ end
21
+
14
22
  options.each do |(field, value)|
15
23
  send("#{field}=", value)
16
24
  end
@@ -30,7 +38,24 @@ module RubyJsonApiClient
30
38
  "#{@namespace}/#{plural.underscore}"
31
39
  end
32
40
 
41
+ def accept_header
42
+ 'application/json'
43
+ end
44
+
45
+ def user_agent
46
+ 'RubyJsonApiClient'
47
+ end
48
+
49
+ def headers
50
+ {
51
+ accept: accept_header,
52
+ user_user: user_agent
53
+ }
54
+ end
55
+
33
56
  def find(klass, id)
57
+ raise "Cannot find nil id" if id.nil?
58
+
34
59
  path = single_path(klass, id: id)
35
60
  status, _, body = http_request(:get, path, {})
36
61
 
@@ -52,13 +77,46 @@ module RubyJsonApiClient
52
77
  end
53
78
  end
54
79
 
80
+ def create(model, data)
81
+ url = collection_path(model.class, {})
82
+ status, _, body = http_request(:post, url, data)
83
+
84
+ if status >= 200 && status <= 299
85
+ body
86
+ else
87
+ raise "Could not post to #{url}"
88
+ end
89
+ end
90
+
91
+ def update(model, data)
92
+ url = single_path(model.class, { id: model.id })
93
+ status, _, body = http_request(:put, url, data)
94
+
95
+ if status >= 200 && status <= 299
96
+ body
97
+ else
98
+ raise "Could not put to #{url}"
99
+ end
100
+ end
101
+
102
+ def delete(model)
103
+ url = single_path(model.class, { id: model.id })
104
+ status, _, body = http_request(:delete, url, {})
105
+
106
+ if status >= 200 && status <= 299
107
+ body
108
+ else
109
+ raise "Could not delete to #{url}"
110
+ end
111
+ end
112
+
55
113
  def get(url)
56
114
  status, _, body = http_request(:get, url, {})
57
115
 
58
116
  if status >= 200 && status <= 299
59
117
  body
60
118
  else
61
- raise "Could not query #{path}"
119
+ raise "Could not query #{url}"
62
120
  end
63
121
  end
64
122
 
@@ -69,10 +127,20 @@ module RubyJsonApiClient
69
127
 
70
128
  proto = uri.scheme || (@secure ? "https" : "http")
71
129
  hostname = uri.host || @hostname
130
+ port = uri.port || @port || (@secure ? 443 : 80)
72
131
  path = uri.path
73
- query_params = (uri.query_values || {}).merge(params)
74
132
 
75
- conn = Faraday.new("#{proto}://#{hostname}")
133
+ query_params = (required_query_params || {})
134
+ .merge(uri.query_values || {})
135
+ .merge(params)
136
+
137
+ conn = Faraday.new("#{proto}://#{hostname}:#{port}", {
138
+ headers: headers
139
+ }) do |f|
140
+ f.request :json
141
+ f.adapter @http_client
142
+ end
143
+
76
144
  response = conn.send(method, path, query_params)
77
145
  [response.status, response.headers, response.body]
78
146
  end
@@ -1,20 +1,27 @@
1
+ require 'active_model'
2
+ require 'active_support'
3
+
1
4
  module RubyJsonApiClient
2
5
  class Base
3
6
  include ActiveModel::Model
4
7
  include ActiveModel::AttributeMethods
8
+ include ActiveModel::Serialization
9
+
10
+ if defined?(ActiveModel::SerializerSupport)
11
+ include ActiveModel::SerializerSupport
12
+ end
5
13
 
6
14
  def self.field(name, type = :string)
7
- @_fields ||= []
8
- @_fields << name
15
+ fields << name
9
16
  attr_accessor name
10
17
  end
11
18
 
12
19
  def self.fields
13
- @_fields || []
20
+ @_fields ||= Set.new [_identifier]
14
21
  end
15
22
 
16
23
  def self.has_field?(name)
17
- (@_fields | [_identifier]).include?(name)
24
+ fields.include?(name)
18
25
  end
19
26
 
20
27
  def self.identifier(name)
@@ -33,11 +40,19 @@ module RubyJsonApiClient
33
40
  def self.has_many(name, options = {})
34
41
  @_has_many_relationships ||= []
35
42
  @_has_many_relationships << name
43
+
36
44
  define_method(name) do
37
- # make cachable
38
- RubyJsonApiClient::Store
39
- .instance
40
- .find_many_relationship(self, name, options)
45
+ @_loaded_has_manys ||= {}
46
+
47
+ if @_loaded_has_manys[name].nil?
48
+ result = RubyJsonApiClient::Store
49
+ .instance
50
+ .find_many_relationship(self, name, options)
51
+
52
+ @_loaded_has_manys[name] = result
53
+ end
54
+
55
+ @_loaded_has_manys[name]
41
56
  end
42
57
  end
43
58
 
@@ -64,6 +79,13 @@ module RubyJsonApiClient
64
79
  @_loaded_has_ones ||= {}
65
80
  @_loaded_has_ones[name] = related
66
81
  end
82
+
83
+ define_method("#{name}_id=".to_sym) do |related_id|
84
+ klass_name = options[:class_name] || ActiveSupport::Inflector.classify(name)
85
+ klass = ActiveSupport::Inflector.constantize(klass_name)
86
+ @_loaded_has_ones ||= {}
87
+ @_loaded_has_ones[name] = klass.new(id: related_id)
88
+ end
67
89
  end
68
90
 
69
91
  def loaded_has_ones
@@ -82,6 +104,10 @@ module RubyJsonApiClient
82
104
  RubyJsonApiClient::Store.instance.query(self, params)
83
105
  end
84
106
 
107
+ def self.create(params)
108
+ new(params).tap(&:save)
109
+ end
110
+
85
111
  def persisted?
86
112
  !!send(self.class._identifier)
87
113
  end
@@ -90,9 +116,28 @@ module RubyJsonApiClient
90
116
  RubyJsonApiClient::Store.instance.reload(self)
91
117
  end
92
118
 
93
- def links
94
- store.find(self.class._identifier).__data__
119
+ def save
120
+ RubyJsonApiClient::Store.instance.save(self)
95
121
  end
96
122
 
123
+ def update_attributes(data)
124
+ data.each do |(key, value)|
125
+ send("#{key}=", value)
126
+ end
127
+ save
128
+ end
129
+
130
+ def destroy
131
+ RubyJsonApiClient::Store.instance.delete(self)
132
+ end
133
+
134
+ def ==(other)
135
+ klass_match = (self.class == other.class)
136
+ ids_match = (send(self.class._identifier) == other.send(other.class._identifier))
137
+
138
+ klass_match && ids_match
139
+ end
140
+ alias_method :eql?, :==
141
+ alias_method :equal?, :==
97
142
  end
98
143
  end
@@ -0,0 +1,9 @@
1
+ module RubyJsonApiClient
2
+ module AmsExtClassMethods
3
+
4
+ def has_one
5
+ super
6
+ end
7
+
8
+ end
9
+ end
@@ -4,20 +4,28 @@ require 'active_support'
4
4
  module RubyJsonApiClient
5
5
  class AmsSerializer
6
6
  attr_accessor :store
7
+ attr_accessor :json_parsing_method
8
+
9
+ def initialize(options = {})
10
+ if options[:json_parsing_method].nil?
11
+ options[:json_parsing_method] = JSON.method(:parse)
12
+ end
13
+
14
+ options.each do |(field, value)|
15
+ send("#{field}=", value)
16
+ end
17
+ end
7
18
 
8
19
  def transform(response)
9
- JSON.parse(response)
20
+ @json_parsing_method.call(response)
10
21
  end
11
22
 
12
- def to_json(model)
23
+ def to_data(model)
13
24
  key = model.class.to_s.underscore.downcase
25
+ id_field = model.class._identifier
14
26
  data = {}
15
27
  data[key] = {}
16
28
 
17
- if model.persisted?
18
- data[key][:id] = model.id
19
- end
20
-
21
29
  # conert fields to json
22
30
  model.class.fields.reduce(data[key]) do |result, field|
23
31
  result[field] = model.send(field)
@@ -33,7 +41,15 @@ module RubyJsonApiClient
33
41
  result
34
42
  end
35
43
 
36
- JSON::generate(data)
44
+ if !model.persisted?
45
+ data[key].delete(id_field) if data[key].has_key?(id_field)
46
+ end
47
+
48
+ data
49
+ end
50
+
51
+ def to_json(model)
52
+ JSON::generate(to_data(model))
37
53
  end
38
54
 
39
55
  def assert(test, failure_message)
@@ -5,6 +5,9 @@ module RubyJsonApiClient
5
5
  class JsonApiSerializer
6
6
  attr_accessor :store
7
7
 
8
+ def initialize(options = {})
9
+ end
10
+
8
11
  def transform(response)
9
12
  JSON.parse(response)
10
13
  end
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  module RubyJsonApiClient
4
2
  class Store
5
3
  def self.register_adapter(name, klass = nil, options = {})
@@ -17,22 +15,25 @@ module RubyJsonApiClient
17
15
  @adapters[name] = OpenStruct.new({ klass: klass, options: options })
18
16
  end
19
17
 
20
-
21
- def self.register_serializer(name, klass = nil)
18
+ def self.register_serializer(name, klass = nil, options = {})
22
19
  @serializers ||= {}
23
20
 
24
- if klass.nil?
21
+ # allow for 2 arguments (automatically figure out class if so)
22
+ if !klass.is_a?(Class)
23
+ # klass is options. autoamtically figure out klass and set options
24
+ temp = klass || options
25
25
  class_name = name.to_s.camelize
26
26
  klass = Kernel.const_get("RubyJsonApiClient::#{class_name}Serializer")
27
+ options = temp
27
28
  end
28
29
 
29
- @serializers[name] = OpenStruct.new({ klass: klass })
30
+ @serializers[name] = OpenStruct.new({ klass: klass, options: options })
30
31
  end
31
32
 
32
33
  def self.get_serializer(name)
33
34
  @serializers ||= {}
34
35
  if @serializers[name]
35
- @serializers[name].klass.new
36
+ @serializers[name].klass.new(@serializers[name].options)
36
37
  end
37
38
  end
38
39
 
@@ -114,7 +115,10 @@ module RubyJsonApiClient
114
115
  response = parent.__origin__
115
116
 
116
117
  # find the relationship
117
- list = serializer.extract_many_relationship(parent, name, options, response)
118
+ list = serializer.extract_many_relationship(parent, name, options, response).map do |model|
119
+ model.__origin__ = response if model.__origin__.nil?
120
+ model
121
+ end
118
122
 
119
123
  # wrap in enumerable proxy to allow reloading
120
124
  collection = RubyJsonApiClient::Collection.new(list)
@@ -130,21 +134,30 @@ module RubyJsonApiClient
130
134
 
131
135
  response = parent.__origin__
132
136
 
133
- serializer.extract_single_relationship(parent, name, options, response)
137
+ serializer.extract_single_relationship(parent, name, options, response).tap do |model|
138
+ model.__origin__ = response if model && model.__origin__.nil?
139
+ end
134
140
  end
135
141
 
142
+ # TODO: make the 2 following functions a bit nicer
136
143
  def load_collection(klass, url)
137
144
  adapter = adapter_for_class(klass)
138
145
  serializer = serializer_for_class(klass)
139
146
  response = adapter.get(url)
140
- serializer.extract_many(klass, response)
147
+ serializer.extract_many(klass, response).map do |model|
148
+ model.__origin__ = response
149
+ model
150
+ end
141
151
  end
142
152
 
153
+ # TODO: make nicer
143
154
  def load_single(klass, id, url)
144
155
  adapter = adapter_for_class(klass)
145
156
  serializer = serializer_for_class(klass)
146
157
  response = adapter.get(url)
147
- serializer.extract_single(klass, id, response)
158
+ serializer.extract_single(klass, id, response).tap do |model|
159
+ model.__origin__ = response
160
+ end
148
161
  end
149
162
 
150
163
  def load(klass, data)
@@ -156,6 +169,45 @@ module RubyJsonApiClient
156
169
  merge(model, new_model)
157
170
  end
158
171
 
172
+ def save(model)
173
+ klass = model.class
174
+ adapter = adapter_for_class(klass)
175
+ serializer = serializer_for_class(klass)
176
+ data = serializer.to_data(model)
177
+
178
+ if model.persisted?
179
+ response = adapter.update(model, data)
180
+ else
181
+ response = adapter.create(model, data)
182
+ end
183
+
184
+ if response && response != ""
185
+ # convert response into model if there is one.
186
+ # should probably rely on adapter status (error, not changed, changed). etc
187
+ #
188
+ # TODO: can we just use serializer to load data into model?
189
+ new_model = serializer.extract_single(klass, model.id, response).tap do |m|
190
+ m.__origin__ = response
191
+ end
192
+
193
+ merge(model, new_model)
194
+ end
195
+
196
+ # TODO: Handle failures
197
+ true
198
+ end
199
+
200
+ def delete(model)
201
+ if model.persisted?
202
+ klass = model.class
203
+ adapter = adapter_for_class(klass)
204
+ adapter.delete(model)
205
+ end
206
+
207
+ # TODO: Handle failures
208
+ true
209
+ end
210
+
159
211
  def merge(into, from)
160
212
  into.__origin__ = from.__origin__
161
213
  into.meta = from.meta
@@ -1,3 +1,3 @@
1
1
  module RubyJsonApiClient
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -1,5 +1,3 @@
1
- require 'active_model'
2
-
3
1
  module RubyJsonApiClient
4
2
  end
5
3
 
@@ -0,0 +1,69 @@
1
+ $: << '../lib/'
2
+ require 'ruby_json_api_client'
3
+ require 'typhoeus/adapters/faraday'
4
+ require 'benchmark/ips'
5
+ require 'webmock'
6
+
7
+ include WebMock::API
8
+
9
+ adapter_options = {
10
+ hostname: 'www.example.com',
11
+ namespace: 'perf',
12
+ secure: true
13
+ }
14
+
15
+
16
+ class Person < RubyJsonApiClient::Base
17
+ field :firstname
18
+ field :lastname
19
+ has_many :items
20
+ end
21
+
22
+ class Item < RubyJsonApiClient::Base
23
+ field :name
24
+ end
25
+
26
+ person_1_response = {
27
+ person: {
28
+ id: 1,
29
+ firstname: 'ryan',
30
+ lastname: 'test',
31
+ links: {
32
+ items: '/perf/items?person_id=1'
33
+ }
34
+ }
35
+ }.to_json
36
+
37
+ stub_request(:get, "https://www.example.com/perf/people/1")
38
+ .to_return(
39
+ status: 200,
40
+ headers: { 'Content-Length' => person_1_response.size },
41
+ body: person_1_response
42
+ )
43
+
44
+
45
+ Benchmark.ips do |x|
46
+ x.report("net_http find") do
47
+ RubyJsonApiClient::Store.register_adapter(
48
+ :ams,
49
+ adapter_options.merge(http_client: :net_http)
50
+ )
51
+ RubyJsonApiClient::Store.register_serializer(:ams)
52
+ RubyJsonApiClient::Store.default(:ams)
53
+
54
+ Person.find(1)
55
+ end
56
+
57
+
58
+
59
+ x.report("typhoeus find") do
60
+ RubyJsonApiClient::Store.register_adapter(
61
+ :ams,
62
+ adapter_options.merge(http_client: :typhoeus)
63
+ )
64
+ RubyJsonApiClient::Store.register_serializer(:ams)
65
+ RubyJsonApiClient::Store.default(:ams)
66
+
67
+ Person.find(1)
68
+ end
69
+ end
data/perf/http.rb ADDED
@@ -0,0 +1,37 @@
1
+ $: << '../lib/'
2
+ require 'ruby_json_api_client'
3
+ require 'typhoeus/adapters/faraday'
4
+ require 'benchmark/ips'
5
+ require 'webmock'
6
+
7
+ include WebMock::API
8
+
9
+ class Person < RubyJsonApiClient::Base
10
+ field :firstname
11
+ field :lastname
12
+ has_many :items
13
+ end
14
+
15
+ stub_request(:get, "https://www.example.com/perf/people/1")
16
+ .to_return(
17
+ status: 200,
18
+ body: {}.to_json
19
+ )
20
+
21
+ adapter = RubyJsonApiClient::RestAdapter.new(
22
+ hostname: 'www.example.com',
23
+ secure: true,
24
+ namespace: 'perf'
25
+ )
26
+
27
+ Benchmark.ips do |x|
28
+ x.report("net http") do
29
+ adapter.http_client = :net_http
30
+ adapter.find(Person, 1)
31
+ end
32
+
33
+ x.report("typhoeus") do
34
+ adapter.http_client = :typhoeus
35
+ adapter.find(Person, 1)
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ $: << '../lib/'
2
+ require 'ruby_json_api_client'
3
+ require 'oj'
4
+ require 'yajl'
5
+ require 'benchmark/ips'
6
+
7
+ json = {
8
+ person: {
9
+ id: 1,
10
+ firstname: 'ryan',
11
+ lastname: 'test',
12
+ links: {
13
+ posts: '/posts?person_id=1'
14
+ }
15
+ }
16
+ }.to_json
17
+
18
+ Benchmark.ips do |x|
19
+ x.report("json serializer") do
20
+ serializer = RubyJsonApiClient::AmsSerializer.new(
21
+ json_parsing_method: JSON.method(:parse)
22
+ )
23
+ serializer.transform(json)
24
+ end
25
+
26
+ x.report("oj serializer") do
27
+ serializer = RubyJsonApiClient::AmsSerializer.new(
28
+ json_parsing_method: Oj.method(:load)
29
+ )
30
+ serializer.transform(json)
31
+ end
32
+
33
+ x.report("yajl") do
34
+ serializer = RubyJsonApiClient::AmsSerializer.new(
35
+ json_parsing_method: Yajl::Parser.method(:parse)
36
+ )
37
+ serializer.transform(json)
38
+ end
39
+ end
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency 'json', '>= 1.8.1'
22
22
  spec.add_dependency 'faraday', '>= 0.9.0'
23
+ spec.add_dependency 'faraday_middleware', '>= 0.9.0'
23
24
  spec.add_dependency 'addressable', '>= 2.3.6'
24
25
  spec.add_dependency 'activemodel', '>= 4.0'
25
26
  spec.add_dependency 'activesupport', '>= 4.0'
@@ -27,8 +28,20 @@ Gem::Specification.new do |spec|
27
28
  spec.add_development_dependency "bundler", "~> 1.6"
28
29
  spec.add_development_dependency "rake"
29
30
  spec.add_development_dependency "pry"
31
+
32
+ # testing
30
33
  spec.add_development_dependency "rspec", "~> 3.0.0"
31
34
  spec.add_development_dependency 'rspec-collection_matchers', '~> 1.0.0'
32
35
  spec.add_development_dependency 'rspec-its', '~> 1.0.1'
33
36
  spec.add_development_dependency 'webmock', '~> 1.18.0'
37
+
38
+ # perf
39
+ spec.add_development_dependency 'benchmark-ips', '~> 2.0.0'
40
+
41
+ # http adapters (for perf testing)
42
+ spec.add_development_dependency 'typhoeus', '~> 0.6.9'
43
+
44
+ # json parsers (for perf testing)
45
+ spec.add_development_dependency 'oj', '~> 2.10.0'
46
+ spec.add_development_dependency 'yajl-ruby', '~> 1.2.1'
34
47
  end
File without changes
@@ -24,3 +24,6 @@ end
24
24
  class Thing < RubyJsonApiClient::Base
25
25
  identifier :uuid
26
26
  end
27
+
28
+ class Nothing < RubyJsonApiClient::Base
29
+ end
@@ -6,7 +6,6 @@ describe RubyJsonApiClient::Base do
6
6
  validates :firstname, presence: true
7
7
  end
8
8
 
9
-
10
9
  describe :field do
11
10
  it "should setup attributes for the model" do
12
11
  person = Person.new
@@ -27,6 +26,18 @@ describe RubyJsonApiClient::Base do
27
26
  end
28
27
  end
29
28
 
29
+ describe :has_field? do
30
+ context "a class with no fields" do
31
+ subject { Nothing.has_field?(:nope) }
32
+ it { should eq(false) }
33
+ end
34
+
35
+ context "a class with fields that has the field" do
36
+ subject { Item.has_field?(:name) }
37
+ it { should eq(true) }
38
+ end
39
+ end
40
+
30
41
  describe :identifer do
31
42
  it "should default to id" do
32
43
  expect(Person._identifier).to eq :id
@@ -57,4 +68,32 @@ describe RubyJsonApiClient::Base do
57
68
  expect(Person.new.persisted?).to eql(false)
58
69
  end
59
70
  end
71
+
72
+ describe :== do
73
+ context "two objects of different classes" do
74
+ subject { Person.new(id: 1) == Item.new(id: 1) }
75
+ it { should eq(false) }
76
+ end
77
+
78
+ context "two objects of the same class but different ids" do
79
+ subject { Person.new(id: 1) == Person.new(id: 2) }
80
+ it { should eq(false) }
81
+ end
82
+
83
+ context "two objects with the same klass and id" do
84
+ subject { Person.new(id: 1) == Person.new(id: 1) }
85
+ it { should eq(true) }
86
+ end
87
+
88
+ context "the same instance" do
89
+ let(:person) { Person.new(id: 1) }
90
+ subject { person == person }
91
+ it { should eq(true) }
92
+ end
93
+
94
+ context "two objects with non standard identifiers" do
95
+ subject { Thing.new(uuid: 'x') == Thing.new(uuid: 'x') }
96
+ it { should eq(true) }
97
+ end
98
+ end
60
99
  end
@@ -184,6 +184,7 @@ describe RubyJsonApiClient::Store do
184
184
 
185
185
  expect(serializer).to receive(:extract_many)
186
186
  .with(Person, "[]")
187
+ .and_return([])
187
188
 
188
189
  store.load_collection(Person, "http://example.com/testings")
189
190
  end
@@ -205,6 +206,7 @@ describe RubyJsonApiClient::Store do
205
206
 
206
207
  expect(serializer).to receive(:extract_single)
207
208
  .with(Person, 1, "{}")
209
+ .and_return(OpenStruct.new)
208
210
 
209
211
  store.load_single(Person, 1, "http://example.com/testings/1")
210
212
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_json_api_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Toronto
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-06 00:00:00.000000000 Z
11
+ date: 2014-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.9.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday_middleware
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.9.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 0.9.0
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: addressable
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +192,62 @@ dependencies:
178
192
  - - "~>"
179
193
  - !ruby/object:Gem::Version
180
194
  version: 1.18.0
195
+ - !ruby/object:Gem::Dependency
196
+ name: benchmark-ips
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 2.0.0
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 2.0.0
209
+ - !ruby/object:Gem::Dependency
210
+ name: typhoeus
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: 0.6.9
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: 0.6.9
223
+ - !ruby/object:Gem::Dependency
224
+ name: oj
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - "~>"
228
+ - !ruby/object:Gem::Version
229
+ version: 2.10.0
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - "~>"
235
+ - !ruby/object:Gem::Version
236
+ version: 2.10.0
237
+ - !ruby/object:Gem::Dependency
238
+ name: yajl-ruby
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - "~>"
242
+ - !ruby/object:Gem::Version
243
+ version: 1.2.1
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - "~>"
249
+ - !ruby/object:Gem::Version
250
+ version: 1.2.1
181
251
  description: API client for activemodel instances
182
252
  email:
183
253
  - ryanto@gmail.com
@@ -196,10 +266,14 @@ files:
196
266
  - lib/ruby_json_api_client/adapters/rest_adapter.rb
197
267
  - lib/ruby_json_api_client/base.rb
198
268
  - lib/ruby_json_api_client/collection.rb
269
+ - lib/ruby_json_api_client/exts/ams_ext.rb
199
270
  - lib/ruby_json_api_client/serializers/ams_serializer.rb
200
271
  - lib/ruby_json_api_client/serializers/json_api_serializer.rb
201
272
  - lib/ruby_json_api_client/store.rb
202
273
  - lib/ruby_json_api_client/version.rb
274
+ - perf/find_model_http_clients.rb
275
+ - perf/http.rb
276
+ - perf/serializer_json.rb
203
277
  - ruby_json_api_client.gemspec
204
278
  - spec/integration/ams/find_spec.rb
205
279
  - spec/integration/ams/has_many_links_spec.rb
@@ -207,6 +281,7 @@ files:
207
281
  - spec/integration/ams/has_one_links_spec.rb
208
282
  - spec/integration/ams/has_one_sideload_spec.rb
209
283
  - spec/integration/ams/query_spec.rb
284
+ - spec/integration/ams/save_spec.rb
210
285
  - spec/integration/json_api/find_spec.rb
211
286
  - spec/integration/json_api/query_spec.rb
212
287
  - spec/spec_helper.rb
@@ -249,6 +324,7 @@ test_files:
249
324
  - spec/integration/ams/has_one_links_spec.rb
250
325
  - spec/integration/ams/has_one_sideload_spec.rb
251
326
  - spec/integration/ams/query_spec.rb
327
+ - spec/integration/ams/save_spec.rb
252
328
  - spec/integration/json_api/find_spec.rb
253
329
  - spec/integration/json_api/query_spec.rb
254
330
  - spec/spec_helper.rb