ruby_json_api_client 0.0.1

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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +25 -0
  6. data/Rakefile +2 -0
  7. data/lib/ruby_json_api_client/adapters/ams_adapter.rb +15 -0
  8. data/lib/ruby_json_api_client/adapters/json_api_adapter.rb +15 -0
  9. data/lib/ruby_json_api_client/adapters/rest_adapter.rb +80 -0
  10. data/lib/ruby_json_api_client/base.rb +98 -0
  11. data/lib/ruby_json_api_client/collection.rb +13 -0
  12. data/lib/ruby_json_api_client/serializers/ams_serializer.rb +187 -0
  13. data/lib/ruby_json_api_client/serializers/json_api_serializer.rb +93 -0
  14. data/lib/ruby_json_api_client/store.rb +171 -0
  15. data/lib/ruby_json_api_client/version.rb +3 -0
  16. data/lib/ruby_json_api_client.rb +14 -0
  17. data/ruby_json_api_client.gemspec +34 -0
  18. data/spec/integration/ams/find_spec.rb +46 -0
  19. data/spec/integration/ams/has_many_links_spec.rb +161 -0
  20. data/spec/integration/ams/has_many_sideload_spec.rb +170 -0
  21. data/spec/integration/ams/has_one_links_spec.rb +57 -0
  22. data/spec/integration/ams/has_one_sideload_spec.rb +87 -0
  23. data/spec/integration/ams/query_spec.rb +65 -0
  24. data/spec/integration/json_api/find_spec.rb +44 -0
  25. data/spec/integration/json_api/query_spec.rb +65 -0
  26. data/spec/spec_helper.rb +7 -0
  27. data/spec/support/classes.rb +26 -0
  28. data/spec/unit/adapters/json_api_spec.rb +4 -0
  29. data/spec/unit/adapters/rest_spec.rb +109 -0
  30. data/spec/unit/base_spec.rb +60 -0
  31. data/spec/unit/collection_spec.rb +17 -0
  32. data/spec/unit/serializers/ams_spec.rb +480 -0
  33. data/spec/unit/serializers/json_api_spec.rb +114 -0
  34. data/spec/unit/store_spec.rb +243 -0
  35. metadata +262 -0
@@ -0,0 +1,171 @@
1
+ require 'spec_helper'
2
+
3
+ module RubyJsonApiClient
4
+ class Store
5
+ def self.register_adapter(name, klass = nil, options = {})
6
+ @adapters ||= {}
7
+
8
+ # allow for 2 arguments (automatically figure out class if so)
9
+ if !klass.is_a?(Class)
10
+ # klass is options. autoamtically figure out klass and set options
11
+ temp = klass || options
12
+ class_name = name.to_s.camelize
13
+ klass = Kernel.const_get("RubyJsonApiClient::#{class_name}Adapter")
14
+ options = temp
15
+ end
16
+
17
+ @adapters[name] = OpenStruct.new({ klass: klass, options: options })
18
+ end
19
+
20
+
21
+ def self.register_serializer(name, klass = nil)
22
+ @serializers ||= {}
23
+
24
+ if klass.nil?
25
+ class_name = name.to_s.camelize
26
+ klass = Kernel.const_get("RubyJsonApiClient::#{class_name}Serializer")
27
+ end
28
+
29
+ @serializers[name] = OpenStruct.new({ klass: klass })
30
+ end
31
+
32
+ def self.get_serializer(name)
33
+ @serializers ||= {}
34
+ if @serializers[name]
35
+ @serializers[name].klass.new
36
+ end
37
+ end
38
+
39
+ def self.get_adapter(name)
40
+ @adapters ||= {}
41
+ if @adapters[name]
42
+ @adapters[name].klass.new(@adapters[name].options)
43
+ end
44
+ end
45
+
46
+ def self.default(format)
47
+ @store = new(format: format)
48
+ end
49
+
50
+ def self.instance
51
+ @store
52
+ end
53
+
54
+ attr_accessor :default_adapter
55
+ attr_accessor :default_serializer
56
+
57
+ def initialize(options)
58
+ if options[:format]
59
+ options[:adapter] = options[:format]
60
+ options[:serializer] = options[:format]
61
+ end
62
+
63
+ @default_adapter = self.class.get_adapter(options[:adapter])
64
+ @default_serializer = self.class.get_serializer(options[:serializer])
65
+
66
+ if @default_serializer && @default_serializer.respond_to?(:store=)
67
+ @default_serializer.store = self
68
+ end
69
+ end
70
+
71
+ def adapter_for_class(klass)
72
+ @default_adapter
73
+ end
74
+
75
+ def serializer_for_class(klass)
76
+ @default_serializer
77
+ end
78
+
79
+ def find(klass, id)
80
+ adapter = adapter_for_class(klass)
81
+ serializer = serializer_for_class(klass)
82
+
83
+ response = adapter.find(klass, id)
84
+ serializer.extract_single(klass, id, response).tap do |model|
85
+ model.__origin__ = response
86
+ end
87
+ end
88
+
89
+ def query(klass, params)
90
+ adapter = adapter_for_class(klass)
91
+ serializer = serializer_for_class(klass)
92
+
93
+ response = adapter.find_many(klass, params)
94
+ list = serializer.extract_many(klass, response)
95
+
96
+ list.each do |model|
97
+ # let the model know where it came from
98
+ model.__origin__ = response
99
+ end
100
+
101
+ # cant tap proxy
102
+ collection = RubyJsonApiClient::Collection.new(list)
103
+ collection.__origin__ = response
104
+ collection
105
+ end
106
+
107
+ def find_many_relationship(parent, name, options)
108
+ # needs to use adapter_for_class
109
+ serializer = @default_serializer
110
+
111
+ # ensure parent is loaded
112
+ reload(parent) if parent.__origin__.nil?
113
+
114
+ response = parent.__origin__
115
+
116
+ # find the relationship
117
+ list = serializer.extract_many_relationship(parent, name, options, response)
118
+
119
+ # wrap in enumerable proxy to allow reloading
120
+ collection = RubyJsonApiClient::Collection.new(list)
121
+ collection.__origin__ = response
122
+ collection
123
+ end
124
+
125
+ def find_single_relationship(parent, name, options)
126
+ # needs to use serializer_for_class
127
+ serializer = @default_serializer
128
+
129
+ reload(parent) if parent.__origin__.nil?
130
+
131
+ response = parent.__origin__
132
+
133
+ serializer.extract_single_relationship(parent, name, options, response)
134
+ end
135
+
136
+ def load_collection(klass, url)
137
+ adapter = adapter_for_class(klass)
138
+ serializer = serializer_for_class(klass)
139
+ response = adapter.get(url)
140
+ serializer.extract_many(klass, response)
141
+ end
142
+
143
+ def load_single(klass, id, url)
144
+ adapter = adapter_for_class(klass)
145
+ serializer = serializer_for_class(klass)
146
+ response = adapter.get(url)
147
+ serializer.extract_single(klass, id, response)
148
+ end
149
+
150
+ def load(klass, data)
151
+
152
+ end
153
+
154
+ def reload(model)
155
+ new_model = find(model.class, model.id)
156
+ merge(model, new_model)
157
+ end
158
+
159
+ def merge(into, from)
160
+ into.__origin__ = from.__origin__
161
+ into.meta = from.meta
162
+
163
+ from.class.fields.reduce(into) do |model, attr|
164
+ call = "#{attr}="
165
+ model.send(call, from.send(attr)) if model.respond_to?(call)
166
+ model
167
+ end
168
+ end
169
+
170
+ end
171
+ end
@@ -0,0 +1,3 @@
1
+ module RubyJsonApiClient
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,14 @@
1
+ require 'active_model'
2
+
3
+ module RubyJsonApiClient
4
+ end
5
+
6
+ require "ruby_json_api_client/version"
7
+ require 'ruby_json_api_client/serializers/json_api_serializer'
8
+ require 'ruby_json_api_client/serializers/ams_serializer'
9
+ require 'ruby_json_api_client/adapters/rest_adapter'
10
+ require 'ruby_json_api_client/adapters/json_api_adapter'
11
+ require 'ruby_json_api_client/adapters/ams_adapter'
12
+ require 'ruby_json_api_client/collection'
13
+ require 'ruby_json_api_client/store'
14
+ require 'ruby_json_api_client/base'
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ruby_json_api_client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ruby_json_api_client"
8
+ spec.version = RubyJsonApiClient::VERSION
9
+ spec.authors = ["Ryan Toronto"]
10
+ spec.email = ["ryanto@gmail.com"]
11
+ spec.summary = %q{API client for activemodel instances}
12
+ spec.description = %q{API client for activemodel instances}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'json', '>= 1.8.1'
22
+ spec.add_dependency 'faraday', '>= 0.9.0'
23
+ spec.add_dependency 'addressable', '>= 2.3.6'
24
+ spec.add_dependency 'activemodel', '>= 4.0'
25
+ spec.add_dependency 'activesupport', '>= 4.0'
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.6"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "pry"
30
+ spec.add_development_dependency "rspec", "~> 3.0.0"
31
+ spec.add_development_dependency 'rspec-collection_matchers', '~> 1.0.0'
32
+ spec.add_development_dependency 'rspec-its', '~> 1.0.1'
33
+ spec.add_development_dependency 'webmock', '~> 1.18.0'
34
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe "AMS find single" do
4
+ context "person" do
5
+
6
+ before(:each) do
7
+ RubyJsonApiClient::Store.register_adapter(:ams, {
8
+ hostname: 'www.example.com',
9
+ namespace: 'testing',
10
+ secure: true
11
+ });
12
+
13
+ RubyJsonApiClient::Store.register_serializer(:ams)
14
+
15
+ RubyJsonApiClient::Store.default(:ams)
16
+ end
17
+
18
+ context "it exists" do
19
+ before(:each) do
20
+ response = {
21
+ person: {
22
+ id: 123,
23
+ firstname: 'ryan',
24
+ lastname: 'test'
25
+ }
26
+ }
27
+
28
+ json = response.to_json
29
+
30
+ stub_request(:get, "https://www.example.com/testing/people/123")
31
+ .to_return(
32
+ status: 200,
33
+ headers: { 'Content-Length' => json.size },
34
+ body: json,
35
+ )
36
+ end
37
+
38
+ context "loads the right model" do
39
+ subject { Person.find(123) }
40
+ its(:id) { should eq(123) }
41
+ its(:firstname) { should eq("ryan") }
42
+ its(:full_name) { should eq("ryan test") }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,161 @@
1
+ require 'spec_helper'
2
+
3
+ describe "AMS load has_many linked records" do
4
+
5
+ before(:each) do
6
+ RubyJsonApiClient::Store.register_adapter(:ams, {
7
+ hostname: 'www.example.com'
8
+ });
9
+
10
+ RubyJsonApiClient::Store.register_serializer(:ams)
11
+
12
+ RubyJsonApiClient::Store.default(:ams)
13
+ end
14
+
15
+ context "that are loaded" do
16
+ before(:each) do
17
+ people_response = {
18
+ people: [{
19
+ id: 123,
20
+ firstname: 'ryan',
21
+ lastname: 'test',
22
+ links: {
23
+ items: "http://www.example.com/items?person=123"
24
+ }
25
+ },{
26
+ id: 456,
27
+ firstname: 'testing',
28
+ lastname: 'again',
29
+ links: {
30
+ items: "/items?person=456"
31
+ }
32
+ }]
33
+ }.to_json
34
+
35
+ items_response = {
36
+ items: [{
37
+ id: 10,
38
+ name: "testing"
39
+ },{
40
+ id: 11,
41
+ name: "another test"
42
+ }]
43
+ }.to_json
44
+
45
+ no_items_response = {
46
+ items: []
47
+ }.to_json
48
+
49
+ stub_request(:get, "http://www.example.com/people")
50
+ .to_return(
51
+ status: 200,
52
+ body: people_response,
53
+ )
54
+
55
+ stub_request(:get, "http://www.example.com/items?person=123")
56
+ .to_return(
57
+ status: 200,
58
+ body: items_response
59
+ )
60
+
61
+ stub_request(:get, "http://www.example.com/items?person=456")
62
+ .to_return(
63
+ status: 200,
64
+ body: no_items_response
65
+ )
66
+ end
67
+
68
+ let(:collection) { Person.all }
69
+ let(:person1) { collection[0] }
70
+ let(:person2) { collection[1] }
71
+
72
+ context "using the first person" do
73
+ subject { person1 }
74
+ its(:id) { should eq(123) }
75
+ its(:items) { should have(2).items }
76
+
77
+ it "should have mappable items" do
78
+ expect(person1.items.map(&:name))
79
+ .to match_array(['testing', 'another test'])
80
+ end
81
+
82
+ context "and their first item" do
83
+ subject { person1.items[0] }
84
+ its(:id) { should eq(10) }
85
+ its(:name) { should eq("testing") }
86
+ end
87
+
88
+ context "and their second item" do
89
+ subject { person1.items[1] }
90
+ its(:id) { should eq(11) }
91
+ its(:name) { should eq("another test") }
92
+ end
93
+ end
94
+ end
95
+
96
+ context "that are not loaded" do
97
+ # this tests Person.new(id: 123).items will load the person
98
+ # then load the items
99
+
100
+ before(:each) do
101
+ person_response = {
102
+ person: {
103
+ id: 123,
104
+ firstname: "ryan",
105
+ links: {
106
+ # use a strange url
107
+ items: "/testing/items?p_id=123"
108
+ }
109
+ }
110
+ }.to_json
111
+
112
+ items_response = {
113
+ items: [{
114
+ id: 1,
115
+ name: "first"
116
+ }, {
117
+ id: 2,
118
+ name: "second"
119
+ }]
120
+ }.to_json
121
+
122
+ stub_request(:get, "http://www.example.com/people/123")
123
+ .to_return(
124
+ status: 200,
125
+ body: person_response,
126
+ )
127
+
128
+ stub_request(:get, "http://www.example.com/testing/items?p_id=123")
129
+ .to_return(
130
+ status: 200,
131
+ body: items_response,
132
+ )
133
+ end
134
+
135
+ let(:person) { Person.new(id: 123) }
136
+ let(:items) { person.items }
137
+
138
+ context "the person" do
139
+ subject { person }
140
+
141
+ before(:each) do
142
+ # load items (which should load person)
143
+ person.items
144
+ end
145
+
146
+ its(:id) { should eq(123) }
147
+ its(:firstname) { should eq('ryan') }
148
+ end
149
+
150
+ context "the items" do
151
+ subject { items }
152
+ it { should have(2).items }
153
+
154
+ context "the first item" do
155
+ subject { items[0] }
156
+ its(:name) { should eq('first') }
157
+ its(:id) { should eq(1) }
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,170 @@
1
+ require 'spec_helper'
2
+
3
+ describe "AMS load has_many sideloaded records" do
4
+
5
+ before(:each) do
6
+ RubyJsonApiClient::Store.register_adapter(:ams, {
7
+ hostname: 'www.example.com'
8
+ });
9
+
10
+ RubyJsonApiClient::Store.register_serializer(:ams)
11
+
12
+ RubyJsonApiClient::Store.default(:ams)
13
+ end
14
+
15
+ context "that are loaded" do
16
+ before(:each) do
17
+ response = {
18
+ people: [{
19
+ id: 123,
20
+ firstname: 'ryan',
21
+ lastname: 'test',
22
+ item_ids: [1,2]
23
+ },{
24
+ id: 456,
25
+ firstname: 'testing',
26
+ lastname: 'again',
27
+ item_ids: [2,3]
28
+ }],
29
+ items: [{
30
+ id: 1,
31
+ name: 'test 1'
32
+ },{
33
+ id: 2,
34
+ name: 'test 2'
35
+ },{
36
+ id: 3,
37
+ name: 'test 3'
38
+ },{
39
+ id: 4,
40
+ name: 'test 4'
41
+ }]
42
+ }.to_json
43
+
44
+ stub_request(:get, "http://www.example.com/people")
45
+ .to_return(
46
+ status: 200,
47
+ body: response,
48
+ )
49
+ end
50
+
51
+ let(:collection) { Person.all }
52
+ let(:person1) { collection[0] }
53
+ let(:person2) { collection[1] }
54
+
55
+ context "using the first person" do
56
+ subject { person1 }
57
+ its(:id) { should eq(123) }
58
+ its(:items) { should have(2).items }
59
+
60
+ it "should have mappable items" do
61
+ expect(person1.items.map(&:name))
62
+ .to match_array(['test 1', 'test 2'])
63
+ end
64
+
65
+ context "and their first item" do
66
+ subject { person1.items[0] }
67
+ its(:id) { should eq(1) }
68
+ its(:name) { should eq("test 1") }
69
+ end
70
+
71
+ context "and their second item" do
72
+ subject { person1.items[1] }
73
+ its(:id) { should eq(2) }
74
+ its(:name) { should eq("test 2") }
75
+ end
76
+ end
77
+ end
78
+
79
+ context "that are not loaded" do
80
+ # this tests Person.new(id: 123).items will load the person
81
+ # then load the items
82
+
83
+ before(:each) do
84
+ response = {
85
+ person: {
86
+ id: 123,
87
+ firstname: "ryan",
88
+ item_ids: [1, 2]
89
+ },
90
+ items: [{
91
+ id: 1,
92
+ name: "first"
93
+ }, {
94
+ id: 2,
95
+ name: "second"
96
+ }]
97
+ }.to_json
98
+
99
+ stub_request(:get, "http://www.example.com/people/123")
100
+ .to_return(
101
+ status: 200,
102
+ body: response,
103
+ )
104
+ end
105
+
106
+ let(:person) { Person.new(id: 123) }
107
+ let(:items) { person.items }
108
+
109
+ context "the person" do
110
+ subject { person }
111
+
112
+ before(:each) do
113
+ # load items (which should load person)
114
+ person.items
115
+ end
116
+
117
+ its(:id) { should eq(123) }
118
+ its(:firstname) { should eq('ryan') }
119
+ end
120
+
121
+ context "the items" do
122
+ subject { items }
123
+ it { should have(2).items }
124
+
125
+ context "the first item" do
126
+ subject { items[0] }
127
+ its(:name) { should eq('first') }
128
+ its(:id) { should eq(1) }
129
+ end
130
+ end
131
+ end
132
+
133
+ context "using a different relationship class name" do
134
+ before(:each) do
135
+ response = {
136
+ person: {
137
+ id: 123,
138
+ firstname: "ryan",
139
+ other_item_ids: [1, 2]
140
+ },
141
+ other_items: [{
142
+ id: 1,
143
+ name: "first"
144
+ },{
145
+ id: 2,
146
+ name: "second"
147
+ }]
148
+ }.to_json
149
+
150
+ stub_request(:get, "http://www.example.com/people/123")
151
+ .to_return(
152
+ status: 200,
153
+ body: response,
154
+ )
155
+ end
156
+
157
+ let(:person) { Person.new(id: 123) }
158
+ let(:other_items) { person.other_items }
159
+
160
+ subject { other_items }
161
+ it { should have(2).items }
162
+
163
+ context "the first item" do
164
+ subject { other_items[0] }
165
+ its(:name) { should eq('first') }
166
+ its(:id) { should eq(1) }
167
+ end
168
+ end
169
+
170
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe "AMS load has_one linked record" do
4
+
5
+ before(:each) do
6
+ RubyJsonApiClient::Store.register_adapter(:ams, {
7
+ hostname: 'www.example.com'
8
+ });
9
+
10
+ RubyJsonApiClient::Store.register_serializer(:ams)
11
+
12
+ RubyJsonApiClient::Store.default(:ams)
13
+ end
14
+
15
+ before(:each) do
16
+ person_response = {
17
+ person: {
18
+ id: 123,
19
+ firstname: 'ryan',
20
+ lastname: 'test',
21
+ links: {
22
+ item: "http://www.example.com/items/10"
23
+ }
24
+ }
25
+ }.to_json
26
+
27
+ item_response = {
28
+ item: {
29
+ id: 10,
30
+ name: "testing"
31
+ }
32
+ }.to_json
33
+
34
+ stub_request(:get, "http://www.example.com/people/123")
35
+ .to_return(
36
+ status: 200,
37
+ body: person_response,
38
+ )
39
+
40
+ stub_request(:get, "http://www.example.com/items/10")
41
+ .to_return(
42
+ status: 200,
43
+ body: item_response
44
+ )
45
+ end
46
+
47
+
48
+ let(:person) { Person.find(123) }
49
+ let(:item) { person.item }
50
+
51
+ context "the item" do
52
+ subject { item }
53
+ it { should be_instance_of(Item) }
54
+ its(:id) { should eq(10) }
55
+ its(:name) { should eq('testing') }
56
+ end
57
+ end