ruby_json_api_client 0.0.1 → 0.0.4

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: 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