ruby_json_api_client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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