brainstem-adaptor 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6746d8612cec0a387c002ad99c0a75615ef2c00c
4
+ data.tar.gz: f9cc76d40aaa1bc0fc53461f6caa7e6e34bbfc68
5
+ SHA512:
6
+ metadata.gz: fa62456fbccacbaa9236b7174acef004b4fdf101a85048432498d802bcf54b4910df7a11306de996467b5e5458ca284dd6d059929b3dc8a5cfc4396158940753
7
+ data.tar.gz: 09887da73e019a9b9ab8bc9b7f2a10b270ca780dcf3aede4cca0be0f2951907106e995eeeb1acb23c1f86ba448505fbe0db72863ab419b1decea0e44b7e65534
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *.gem
2
+ .yardoc
3
+ rdoc
4
+ coverage
5
+ .bundle
6
+ .ruby-version
7
+ .rvmrc
8
+ .idea
9
+ Gemfile.lock
10
+
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.0.0"
4
+ - "1.9.3"
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
3
+ gem 'rake'
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ ## Ruby Brainstem Adaptor
2
+ [![Build Status](https://travis-ci.org/einzige/brainstem-adaptor.svg?branch=master)](https://travis-ci.org/einzige/brainstem-adaptor)
3
+ [![Dependency Status](https://gemnasium.com/einzige/brainstem-ruby.svg)](https://gemnasium.com/einzige/brainstem-ruby)
4
+
5
+ Brainstem Adaptor provides an easy to use interface for [Brainstem](https://github.com/mavenlink/brainstem) API.
6
+
7
+ ```ruby
8
+ BrainstemAdaptor.load_specification('my_api_service.yml')
9
+ response_data = MyApi.get('/users.json')
10
+
11
+ # response_data can be a JSON string or Hash
12
+ response = BrainstemAdaptor::Response.new(response_data)
13
+
14
+ response.count # returns total count as Integer
15
+ # ...
16
+ response.results # returns Array<BrainstemAdaptor::Record>
17
+ response.results[0]['friends'] # returns Array<BrainstemAdaptor::Record>
18
+
19
+ response.results[0]['friends'].last['name']
20
+
21
+ response.results[0]['name'] # returns String
22
+ response.results[0].id # returns String or Integer
23
+ response.results[0]['mom'] # returns BrainstemAdaptor::Record
24
+
25
+ response.results[0]['mom']['name']
26
+ # ...
27
+ response['users'] # returns plain Hash as is from response
28
+ response['count'] # same here, Integer
29
+ ```
30
+
31
+ ## Installation
32
+
33
+ Run:
34
+ ```bash
35
+ gem install brainstem-adaptor
36
+ ```
37
+
38
+ Or put in your Gemfile:
39
+ ```ruby
40
+ gem 'brainstem-adaptor'
41
+ ```
42
+
43
+ ## Configuration
44
+ #### Create specification for your API.
45
+ The only missing thing in Brainstem responses is an __associations information__.
46
+ Having `customer_ids` field does not guarantee that customer ids are related to `customers` collection, those may also be ids of `users` collection.
47
+ Specification is the thing which describes missing information about responses you receive.
48
+ You can also put any additional information you need in your specification (see details below).
49
+
50
+ ```yaml
51
+ ---
52
+ # my_api_service.yml file
53
+ users:
54
+ associations:
55
+ friends:
56
+ foreign_key: friend_ids
57
+ collection: 'users'
58
+ mom:
59
+ foreign_key: mom_id
60
+ collection: 'users'
61
+
62
+ projects:
63
+ fields:
64
+ name:
65
+ type: string
66
+ required: true
67
+ employee_ids:
68
+ type: array
69
+
70
+ associations:
71
+ employees:
72
+ foreign_key: employee_ids
73
+ collection: users
74
+ ```
75
+
76
+ ### Multiple specifications
77
+
78
+ You may also have multiple APIs which have different specificaitons.
79
+
80
+ ```ruby
81
+ BrainstemAdaptor::Specification[:my_tracker_api] = { 'users' => {} } # ...
82
+ response_data = MyApi.get('/users.json')
83
+
84
+ response = BrainstemAdaptor::Response.new(response_data, BrainstemAdaptor::Specification[:my_tracker_api])
85
+ # ...
86
+ ```
87
+
88
+ ## Contributing
89
+
90
+ 1. Fork
91
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
92
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
93
+ 4. Push to the branch (`git push origin my-new-feature`)
94
+ 5. Create new Pull Request (`git pull-request`)
95
+
96
+ ## License
97
+
98
+ Brainstem Adaptor was created by Mavenlink, Inc. and are available under the MIT License.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new :spec
5
+ task :default => :spec
6
+
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{brainstem-adaptor}
5
+ s.version = "0.0.3"
6
+
7
+ s.date = %q{2014-03-10}
8
+ s.authors = ["Sergei Zinin (einzige)"]
9
+ s.email = %q{szinin@gmail.com}
10
+ s.homepage = %q{http://github.com/einzige/brainstem-adaptor}
11
+
12
+ s.licenses = ["MIT"]
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.require_paths = ["lib"]
16
+ s.extra_rdoc_files = ["README.md"]
17
+
18
+ s.description = %q{Parses Brainstem responses, makes it convenient to organize access to your data.}
19
+ s.summary = %q{Brainstem API Adaptor}
20
+
21
+ s.add_development_dependency 'rspec'
22
+ s.add_runtime_dependency 'activesupport', ">= 3.0.0"
23
+ end
24
+
25
+
@@ -0,0 +1,40 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+ require 'active_support/core_ext/hash/slice'
5
+ require 'active_support/core_ext/object/try'
6
+ require 'brainstem_adaptor/specification'
7
+ require 'brainstem_adaptor/association'
8
+ require 'brainstem_adaptor/record'
9
+ require 'brainstem_adaptor/invalid_response_error'
10
+ require 'brainstem_adaptor/response'
11
+
12
+ module BrainstemAdaptor
13
+ VERSION = '0.0.3'
14
+
15
+ def self.parser
16
+ @parser ||= JSON
17
+ end
18
+
19
+ # @param parser Any JSON parser
20
+ # @raise [ArgumentError] if parser does not respond to #parse
21
+ def self.parser=(parser)
22
+ raise ArgumentError, 'Parser must respond to #parse message' unless parser.respond_to?(:parse)
23
+ @parser = parser
24
+ end
25
+
26
+ # @return [BrainstemAdaptor::Specification]
27
+ def self.default_specification
28
+ BrainstemAdaptor::Specification[:default]
29
+ end
30
+
31
+ # @param specification [Hash]
32
+ def self.specification=(specification)
33
+ BrainstemAdaptor::Specification[:default] = specification
34
+ end
35
+
36
+ # @param path [String] Path to YML specification file
37
+ def self.load_specification(path)
38
+ self.specification = YAML::load_file(path)
39
+ end
40
+ end
@@ -0,0 +1,73 @@
1
+ module BrainstemAdaptor
2
+ class Association
3
+ include Enumerable
4
+
5
+ attr_reader :name, :record, :specification, :collection_name, :foreign_key
6
+
7
+ # @param record [BrainstemAdaptor::Record]
8
+ # @param name [String, Symbol]
9
+ def initialize(record, name)
10
+ @name = name.to_s
11
+
12
+ unless record.has_association?(@name)
13
+ raise ArgumentError, "No '#@name' specification found for #{record.collection_name}"
14
+ end
15
+
16
+ @record = record
17
+ @specification = record.associations_specification[@name]
18
+ @collection_name = @specification['collection'] || @name
19
+ @foreign_key = @specification['foreign_key']
20
+ end
21
+
22
+ # @param other [Enumerable]
23
+ # @return [true, false]
24
+ def ==(other)
25
+ other == each.to_a
26
+ end
27
+
28
+ # @param order [Integer] Index in collection starting from zero
29
+ def [](order)
30
+ records[order]
31
+ end
32
+
33
+ # @return [Enumerable]
34
+ def each(&block)
35
+ records.each(&block)
36
+ end
37
+
38
+ def has_many?
39
+ ids.is_a?(Array)
40
+ end
41
+
42
+ def has_one?
43
+ !has_many?
44
+ end
45
+
46
+ # Checks if association has been included in request
47
+ # @return [true, false]
48
+ def loaded?
49
+ (!record[foreign_key].nil?) && (!!record.response[@collection_name].try(:any?))
50
+ end
51
+
52
+ # @return [Array<BrainstemAdaptor::Record>]
53
+ def records
54
+ [*ids].map do |id|
55
+ BrainstemAdaptor::Record.new(collection_name, id, record.response)
56
+ end
57
+ end
58
+ alias_method :all, :records
59
+
60
+ # Returns relation object for has_many associations and record for has_one
61
+ # Acts as AR::find for has_one associations, as AR::where for has_many
62
+ # @return [BrainstemAdaptor::Record, self]
63
+ def reflect
64
+ has_many? ? self : records.first
65
+ end
66
+
67
+ private
68
+
69
+ def ids # TODO(SZ): move to public?
70
+ record[foreign_key]
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,12 @@
1
+ module BrainstemAdaptor
2
+ class InvalidResponseError < StandardError
3
+ attr_reader :response
4
+
5
+ # @param [Hash] response
6
+ # @param [Stirng] message
7
+ def initialize(response, message = response.inspect)
8
+ @response = response
9
+ super(message)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,74 @@
1
+ module BrainstemAdaptor
2
+ class Record < ActiveSupport::HashWithIndifferentAccess
3
+ attr_reader :collection_name, :id, :response
4
+
5
+ # @param collection_name [String, Symbol]
6
+ # @param id
7
+ # @param response [BrainstemAdaptor::Response, nil]
8
+ def initialize(collection_name, id, response = nil)
9
+ super([])
10
+ @collection_name = collection_name.to_s
11
+ @id = id
12
+ load_fields_with(response) if response
13
+ end
14
+
15
+ # @param key [String]
16
+ def [](key)
17
+ if has_key?(key = key.to_s)
18
+ super
19
+ elsif has_association?(key)
20
+ association_by_name(key).reflect
21
+ end
22
+ end
23
+
24
+ # @return [Hash]
25
+ def associations_specification
26
+ @associations_specification ||= specification['associations'] || {}
27
+ end
28
+
29
+ # @param name [String]
30
+ # @raise [ArgumentError] if name is not related to any association
31
+ # @return [BrainstemAdaptor::Association]
32
+ def association_by_name(name)
33
+ if has_association?(name)
34
+ (@associations ||= {})[name] ||= BrainstemAdaptor::Association.new(self, name)
35
+ end
36
+ end
37
+
38
+ # @param name [String]
39
+ def has_association?(name)
40
+ associations_specification.has_key?(name.to_s)
41
+ end
42
+
43
+ # @return [Hash]
44
+ def specification
45
+ @specification ||= {}
46
+ end
47
+
48
+ protected
49
+
50
+ # @param response [Brainstem::Response]
51
+ # @param fields_to_reload [Array] Reloads all fields if empty
52
+ def load_fields_with(response, fields_to_reload = [])
53
+ @response = response
54
+
55
+ if @response.specification.has_key?(collection_name)
56
+ @specification = @response.specification[collection_name] || {}
57
+ else
58
+ raise BrainstemAdaptor::InvalidResponseError, "Can't find '#{collection_name}' association in specification"
59
+ end
60
+
61
+ collection = @response[@collection_name] or
62
+ raise BrainstemAdaptor::InvalidResponseError, "No such collection #@collection_name"
63
+
64
+ fields = collection[@id] or
65
+ raise BrainstemAdaptor::InvalidResponseError, "No such record #{@collection_name}##{@id}"
66
+
67
+ if fields_to_reload.any?
68
+ merge!(fields.slice(*fields_to_reload))
69
+ else
70
+ merge!(fields)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,57 @@
1
+ module BrainstemAdaptor
2
+ class Response
3
+ attr_reader :response_data, :specification
4
+
5
+ # @param response_data [String, Hash]
6
+ # @param specification [BrainstemAdaptor::Specification]
7
+ def initialize(response_data, specification = BrainstemAdaptor.default_specification)
8
+ @specification = specification or raise ArgumentError, 'Specification is not set'
9
+
10
+ case response_data
11
+ when String
12
+ @response_data = BrainstemAdaptor.parser.parse(response_data)
13
+ when Hash
14
+ @response_data = response_data
15
+ else
16
+ raise ArgumentError, "Expected String, got #{@response_data.class.name}"
17
+ end
18
+
19
+ raise InvalidResponseError, "count isn't returned" unless @response_data['count']
20
+ raise InvalidResponseError, "results collection isn't returned" unless @response_data['results']
21
+ raise InvalidResponseError, "results collection is not an array" unless @response_data['results'].is_a?(Array)
22
+
23
+ rescue JSON::ParserError => e
24
+ raise BrainstemAdaptor::InvalidResponseError, response_data, e.message
25
+ end
26
+
27
+ # @param key [String, Symbol]
28
+ # @return [Hash, nil]
29
+ def [](key)
30
+ response_data[key.to_s]
31
+ end
32
+
33
+ # Returns __TOTAL__ number of records
34
+ # @return [Integer]
35
+ def count
36
+ response_data['count']
37
+ end
38
+ alias_method :total_count, :count
39
+
40
+ # @return [String]
41
+ def to_hash
42
+ response_data
43
+ end
44
+
45
+ # Returns results has with proper ordering
46
+ # @return [Array<BrainstemAdaptor::Record>]
47
+ def results
48
+ self['results'].map do |result|
49
+ BrainstemAdaptor::Record.new(result['key'], result['id'], self)
50
+ end
51
+ end
52
+
53
+ def ==(other)
54
+ other == response_data
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,27 @@
1
+ module BrainstemAdaptor
2
+ class Specification < Hash
3
+
4
+ # @param specification [Hash]
5
+ def initialize(specification)
6
+ super
7
+ self.merge!(specification)
8
+ end
9
+
10
+ # @return [Hash]
11
+ def self.instances
12
+ @instances ||= {}
13
+ end
14
+
15
+ # @param key [Symbol] Specification name
16
+ # @return [BrainstemAdaptor::Specification]
17
+ def self.[](key)
18
+ self.instances[key.to_sym] or raise ArgumentError, "No such specification '#{key}'"
19
+ end
20
+
21
+ # @param key [Symbol] Specification name
22
+ # @param value [Hash] Specification body
23
+ def self.[]=(key, value)
24
+ self.instances[key.to_sym] = self.new(value)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,243 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrainstemAdaptor::Association do
4
+ let(:nickolai) { { 'name' => 'Nickolai', 'friend_ids' => ['13', '14'], 'enemy_id' => '14' } }
5
+ let(:ivan) { { 'name' => 'Ivan', 'friend_ids' => [], 'enemy_id' => '12' } }
6
+ let(:anatolii) { { 'name' => 'Anatolii', 'friend_ids' => ['13', '14'], 'enemy_id' => '12' } }
7
+
8
+ let(:response_data) do
9
+ {
10
+ 'count' => 0,
11
+ 'results' => [],
12
+ 'users' => {
13
+ '12' => nickolai,
14
+ '13' => ivan,
15
+ '14' => anatolii
16
+ }
17
+ }
18
+ end
19
+
20
+ let(:specification) do
21
+ {
22
+ 'users' => {
23
+ 'fields' => {
24
+ 'name' => {}
25
+ },
26
+ 'associations' => {
27
+ 'friends' => {
28
+ 'foreign_key' => 'friend_ids',
29
+ 'collection' => 'users'
30
+ },
31
+ 'enemy' => {
32
+ 'foreign_key' => 'enemy_id',
33
+ 'collection' => 'users'
34
+ },
35
+ 'mother_in_law' => {
36
+ 'foreign_key' => 'mother_in_law_id',
37
+ 'collection' => 'users'
38
+ },
39
+ 'some_empty' => {
40
+ 'foreign_key' => 'friend_ids',
41
+ 'collection' => 'some_empty_collection'
42
+ }
43
+ }
44
+ }
45
+ }
46
+ end
47
+
48
+ let(:response) do
49
+ BrainstemAdaptor::Response.new(response_data, specification)
50
+ end
51
+
52
+ let(:nickolai_user) { BrainstemAdaptor::Record.new('users', '12', response) }
53
+ let(:ivan_user) { BrainstemAdaptor::Record.new('users', '13', response) }
54
+ let(:anatolii_user) { BrainstemAdaptor::Record.new('users', '14', response) }
55
+
56
+ let(:nickolai_friends) { described_class.new(nickolai_user, 'friends') }
57
+ let(:ivan_friends) { described_class.new(ivan_user, 'friends') }
58
+ let(:anatoliy_friends) { described_class.new(anatolii_user, 'friends') }
59
+
60
+ let(:nickolai_enemy) { described_class.new(nickolai_user, 'enemy') }
61
+ let(:ivan_enemy) { described_class.new(ivan_user, 'enemy') }
62
+ let(:anatoliy_enemy) { described_class.new(anatolii_user, 'enemy') }
63
+
64
+ describe '#==' do
65
+ context 'has_many relations' do
66
+ specify do
67
+ expect(nickolai_friends).to eq(anatoliy_friends)
68
+ end
69
+
70
+ specify do
71
+ expect(nickolai_friends).not_to eq(ivan_friends)
72
+ end
73
+
74
+ specify do
75
+ expect(nickolai_friends).to eq(nickolai_friends)
76
+ end
77
+
78
+ specify do
79
+ expect(ivan_friends).not_to eq(nickolai_friends)
80
+ end
81
+
82
+ specify do
83
+ expect(ivan_friends).not_to eq(anatoliy_friends)
84
+ end
85
+
86
+ specify do
87
+ expect(ivan_friends).to eq(ivan_friends)
88
+ end
89
+
90
+ specify do
91
+ expect(anatoliy_friends).to eq(nickolai_friends)
92
+ end
93
+
94
+ specify do
95
+ expect(anatoliy_friends).not_to eq(ivan_friends)
96
+ end
97
+
98
+ specify do
99
+ expect(anatoliy_friends).to eq(anatoliy_friends)
100
+ end
101
+ end
102
+
103
+ context 'has_one relations' do
104
+ specify do
105
+ expect(nickolai_enemy).not_to eq(anatoliy_enemy)
106
+ end
107
+
108
+ specify do
109
+ expect(nickolai_enemy).not_to eq(ivan_enemy)
110
+ end
111
+
112
+ specify do
113
+ expect(nickolai_enemy).to eq(nickolai_enemy)
114
+ end
115
+
116
+ specify do
117
+ expect(ivan_enemy).not_to eq(nickolai_enemy)
118
+ end
119
+
120
+ specify do
121
+ expect(ivan_enemy).to eq(anatoliy_enemy)
122
+ end
123
+
124
+ specify do
125
+ expect(ivan_enemy).to eq(ivan_enemy)
126
+ end
127
+
128
+ specify do
129
+ expect(anatoliy_enemy).not_to eq(nickolai_enemy)
130
+ end
131
+
132
+ specify do
133
+ expect(anatoliy_enemy).to eq(ivan_enemy)
134
+ end
135
+
136
+ specify do
137
+ expect(anatoliy_enemy).to eq(anatoliy_enemy)
138
+ end
139
+ end
140
+ end
141
+
142
+ describe '#[]' do
143
+ specify do
144
+ expect(nickolai_friends[0]).to eq(ivan)
145
+ end
146
+
147
+ specify do
148
+ expect(nickolai_friends[0]).to eq(ivan_user)
149
+ end
150
+
151
+ specify do
152
+ expect(nickolai_friends[1]).to eq(anatolii)
153
+ end
154
+
155
+ specify do
156
+ expect(nickolai_friends[1]).to eq(anatolii_user)
157
+ end
158
+
159
+ specify do
160
+ expect(nickolai_friends[2]).to be_nil
161
+ end
162
+
163
+ specify do
164
+ expect(nickolai_enemy[0]).to eq(anatolii)
165
+ end
166
+
167
+ specify do
168
+ expect(nickolai_enemy[0]).to eq(anatolii_user)
169
+ end
170
+
171
+ specify do
172
+ expect(nickolai_enemy[1]).to be_nil
173
+ end
174
+ end
175
+
176
+ describe '#each' do
177
+ specify do
178
+ expect(nickolai_friends.each).to be_a Enumerable
179
+ end
180
+ end
181
+
182
+ describe '#records' do
183
+ specify do
184
+ expect(nickolai_friends.records).to eq([ivan, anatolii])
185
+ end
186
+
187
+ specify do
188
+ expect(nickolai_enemy.records).to eq([anatolii])
189
+ end
190
+ end
191
+
192
+ describe '#reflect' do
193
+ specify do
194
+ expect(nickolai_friends.reflect).to eq(nickolai_friends)
195
+ end
196
+
197
+ specify do
198
+ expect(nickolai_enemy.reflect).to eq(anatolii)
199
+ end
200
+ end
201
+
202
+ context 'association is not included in specification' do
203
+ subject { described_class.new(nickolai_user, 'something wrong') }
204
+
205
+ specify do
206
+ expect { subject }.to raise_error ArgumentError, /specification/
207
+ end
208
+ end
209
+
210
+ describe '#has_many?' do
211
+ specify do
212
+ expect(nickolai_friends.has_many?).to eq(true)
213
+ end
214
+
215
+ specify do
216
+ expect(nickolai_enemy.has_many?).to eq(false)
217
+ end
218
+ end
219
+
220
+ describe '#has_one?' do
221
+ specify do
222
+ expect(nickolai_friends.has_one?).to eq(false)
223
+ end
224
+
225
+ specify do
226
+ expect(nickolai_enemy.has_one?).to eq(true)
227
+ end
228
+ end
229
+
230
+ describe '#loaded?' do
231
+ specify do
232
+ expect(nickolai_user.association_by_name('mother_in_law').loaded?).to eq(false)
233
+ end
234
+
235
+ specify do
236
+ expect(nickolai_user.association_by_name('friends').loaded?).to eq(true)
237
+ end
238
+
239
+ specify do
240
+ expect(nickolai_user.association_by_name('some_empty').loaded?).to eq(false)
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrainstemAdaptor::InvalidResponseError do
4
+ subject { described_class.new(response) }
5
+ let(:response) { {failed: true} }
6
+
7
+ its(:response) { should == response }
8
+ its(:message) { should include('failed') }
9
+
10
+ specify do
11
+ expect { raise BrainstemAdaptor::InvalidResponseError, 'failed' }.to raise_error(described_class)
12
+ end
13
+ end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrainstemAdaptor::Record do
4
+ let(:response_data) do
5
+ {
6
+ 'count' => 0,
7
+ 'results' => [],
8
+ 'users' => {
9
+ '12' => { 'name' => 'Petr', 'friend_ids' => ['13'], 'enemy_id' => nil },
10
+ '13' => { 'name' => 'Pelat', 'friend_ids' => [], 'enemy_id' => '12' },
11
+ }
12
+ }
13
+ end
14
+
15
+ let(:specification) do
16
+ {
17
+ 'users' => {
18
+ 'fields' => {
19
+ 'name' => {}
20
+ },
21
+ 'associations' => {
22
+ 'friends' => {
23
+ 'foreign_key' => 'friend_ids',
24
+ 'collection' => 'users'
25
+ },
26
+ 'enemy' => {
27
+ 'foreign_key' => 'enemy_id',
28
+ 'collection' => 'users'
29
+ }
30
+ }
31
+ }
32
+ }
33
+ end
34
+
35
+ let(:response) do
36
+ BrainstemAdaptor::Response.new(response_data, specification)
37
+ end
38
+
39
+ subject { described_class.new('users', '12', response) }
40
+
41
+ its(:id) { should == '12' }
42
+ its(:collection_name) { should == 'users' }
43
+ its(:response) { should == response }
44
+ its(:associations_specification) { should == specification['users']['associations'] }
45
+
46
+ context 'invalid response' do
47
+ context 'collection is not described in specification' do
48
+ let(:specification) { {} }
49
+
50
+ specify do
51
+ expect { subject }.to raise_error BrainstemAdaptor::InvalidResponseError, /association/
52
+ end
53
+ end
54
+
55
+ context 'collection is not included in response' do
56
+ let(:response_data) { {'count' => 0, 'results' => []} }
57
+
58
+ specify do
59
+ expect { subject }.to raise_error BrainstemAdaptor::InvalidResponseError, /collection/
60
+ end
61
+ end
62
+
63
+ context 'record is not listed in collection' do
64
+ let(:response_data) { {'count' => 0, 'results' => [], 'users' => {} } }
65
+
66
+ specify do
67
+ expect { subject }.to raise_error BrainstemAdaptor::InvalidResponseError, /record/
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#[]' do
73
+ specify do
74
+ expect(subject['name']).to eq('Petr')
75
+ end
76
+
77
+ specify do
78
+ expect(subject['friends']).to eq([{'name' => 'Pelat', 'friend_ids' => [], 'enemy_id' => '12'}])
79
+ end
80
+
81
+ specify do
82
+ expect(subject['enemy']).to be_nil
83
+ end
84
+
85
+ context 'inverse' do
86
+ subject { described_class.new('users', '13', response) }
87
+
88
+ specify do
89
+ expect(subject['name']).to eq('Pelat')
90
+ end
91
+
92
+ specify do
93
+ expect(subject['friends']).to be_a Enumerable
94
+ end
95
+
96
+ specify do
97
+ expect(subject['friends'].any?).to eq(false)
98
+ end
99
+
100
+ specify do
101
+ expect(subject['enemy']).to be_a BrainstemAdaptor::Record
102
+ end
103
+
104
+ specify do
105
+ expect(subject['enemy']).to eq({'name' => 'Petr', 'friend_ids' => ['13'], 'enemy_id' => nil})
106
+ end
107
+ end
108
+ end
109
+
110
+ describe '#has_association?' do
111
+ specify do
112
+ expect(subject.has_association?('friends')).to eq(true)
113
+ end
114
+
115
+ specify do
116
+ expect(subject.has_association?('something invalid')).to eq(false)
117
+ end
118
+ end
119
+
120
+ describe '#association_by_name' do
121
+ context 'association exists' do
122
+ specify do
123
+ expect(subject.association_by_name('friends')).to be_a BrainstemAdaptor::Association
124
+ end
125
+ end
126
+
127
+ context 'association does not exist' do
128
+ specify do
129
+ expect(subject.association_by_name('something wrong')).to be_nil
130
+ end
131
+ end
132
+ end
133
+
134
+ context 'no specification set' do
135
+ specify do
136
+ expect { described_class.new('test', 1)['any key'] }.not_to raise_error
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,197 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrainstemAdaptor::Response do
4
+ let(:plain_text_response) do
5
+ <<-JSON_RESPONSE
6
+ {
7
+ "count": 99,
8
+ "results": [{ "key": "workspaces", "id": "10" }, { "key": "workspaces", "id": "11" }],
9
+ "workspaces": {
10
+ "10": {
11
+ "id": "10",
12
+ "title": "some project",
13
+ "participant_ids": ["2", "6"],
14
+ "primary_counterpart_id": "6"
15
+ },
16
+ "11": {
17
+ "id": "11",
18
+ "title": "another project",
19
+ "participant_ids": ["2", "8"],
20
+ "primary_counterpart_id": "8"
21
+ }
22
+ },
23
+ "users": {
24
+ "2": { "id": "2", "full_name": "bob" },
25
+ "6": { "id": "6", "full_name": "chaz" },
26
+ "8": { "id": "8", "full_name": "jane" }
27
+ }
28
+ }
29
+ JSON_RESPONSE
30
+ end
31
+
32
+ let(:response_hash) { BrainstemAdaptor.parser.parse(plain_text_response) }
33
+
34
+ let(:specification) do
35
+ {
36
+ 'workspaces' => {
37
+ 'associations' => {
38
+ 'participants' => {
39
+ 'foreign_key' => 'participant_ids',
40
+ 'collection' => 'users'
41
+ },
42
+ 'primary_counterpart' => {
43
+ 'foreign_key' => 'primary_counterpart_id',
44
+ 'collection' => 'users'
45
+ }
46
+ }
47
+ },
48
+ 'users' => nil
49
+ }
50
+ end
51
+
52
+ before do
53
+ BrainstemAdaptor.specification = specification
54
+ end
55
+
56
+ let(:response_data) { plain_text_response }
57
+
58
+ subject(:response) { described_class.new(response_data) }
59
+
60
+ describe 'collection' do
61
+ its(:count) { should == 99 }
62
+
63
+ specify do
64
+ expect(response.results).to have(2).records
65
+ end
66
+
67
+ describe 'records data' do
68
+ specify do
69
+ expect(response.results[0]).to be_a BrainstemAdaptor::Record
70
+ end
71
+
72
+ specify do
73
+ expect(response.results[1]).to be_a BrainstemAdaptor::Record
74
+ end
75
+
76
+ specify do
77
+ expect(response.results[0].collection_name).to eq('workspaces')
78
+ end
79
+
80
+ specify do
81
+ expect(response.results[1].collection_name).to eq('workspaces')
82
+ end
83
+
84
+ specify do
85
+ expect(response.results[0]).to eq(response_hash['workspaces']['10'])
86
+ end
87
+
88
+ specify do
89
+ expect(response.results[1]).to eq(response_hash['workspaces']['11'])
90
+ end
91
+ end
92
+ end
93
+
94
+ describe 'associations' do
95
+ specify do
96
+ expect(response['users']).to eq(response_hash['users'])
97
+ end
98
+
99
+ describe '"has many" relations' do
100
+ specify do
101
+ expect(response.results[0]['participants'][0]).to be_a BrainstemAdaptor::Record
102
+ end
103
+
104
+ specify do
105
+ expect(response.results[0]['participants'][1]).to be_a BrainstemAdaptor::Record
106
+ end
107
+
108
+ specify do
109
+ expect(response.results[1]['participants'][0]).to be_a BrainstemAdaptor::Record
110
+ end
111
+
112
+ specify do
113
+ expect(response.results[1]['participants'][1]).to be_a BrainstemAdaptor::Record
114
+ end
115
+
116
+ specify do
117
+ expect(response.results[0]['participants']).to eq([response_hash['users']['2'], response_hash['users']['6']])
118
+ end
119
+
120
+ specify do
121
+ expect(response.results[1]['participants']).to eq([response_hash['users']['2'], response_hash['users']['8']])
122
+ end
123
+ end
124
+
125
+ describe '"has one" relations' do
126
+ specify do
127
+ expect(response.results[0]['primary_counterpart']).to be_a BrainstemAdaptor::Record
128
+ end
129
+
130
+ specify do
131
+ expect(response.results[1]['primary_counterpart']).to be_a BrainstemAdaptor::Record
132
+ end
133
+ end
134
+ end
135
+
136
+ context 'invalid JSON format' do
137
+ let(:response_data) { 'test; invalid " json ' }
138
+
139
+ specify do
140
+ expect { subject }.to raise_error BrainstemAdaptor::InvalidResponseError
141
+ end
142
+ end
143
+
144
+ context 'parsed input' do
145
+ let(:response_data) { response_hash }
146
+
147
+ specify do
148
+ expect(subject.response_data).to eq(response_data)
149
+ end
150
+ end
151
+
152
+ context 'invalid input' do
153
+ let(:response_data) { nil }
154
+
155
+ specify do
156
+ expect { subject }.to raise_error ArgumentError, /Expected String/
157
+ end
158
+
159
+ context 'count is not returned' do
160
+ let(:response_data) { { 'results' => [] } }
161
+
162
+ specify do
163
+ expect { subject }.to raise_error BrainstemAdaptor::InvalidResponseError, /count/
164
+ end
165
+ end
166
+
167
+ context 'results collection is not returned' do
168
+ let(:response_data) { { 'count' => 0 } }
169
+
170
+ specify do
171
+ expect { subject }.to raise_error BrainstemAdaptor::InvalidResponseError, /results.*returned/
172
+ end
173
+ end
174
+
175
+ context 'results collection is not an array' do
176
+ let(:response_data) { { 'count' => 0, 'results' => {} } }
177
+
178
+ specify do
179
+ expect { subject }.to raise_error BrainstemAdaptor::InvalidResponseError, /results.*array/
180
+ end
181
+ end
182
+ end
183
+
184
+ describe '#to_hash' do
185
+ specify do
186
+ expect(subject.to_hash).to eq(response_hash)
187
+ end
188
+ end
189
+
190
+ describe '#==' do
191
+ let(:response_data) { { 'count' => 1, 'results' => [{'key' => 'user', 'value' => 2}] } }
192
+
193
+ specify do
194
+ expect(subject).to eq(response_data)
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrainstemAdaptor::Specification do
4
+ describe '.[]' do
5
+ before do
6
+ described_class[:test] = {stored: true}
7
+ described_class[:another_test] = {something_else: false}
8
+ end
9
+
10
+ it 'stores multiple specifications' do
11
+ expect(described_class[:test]).to eq({stored: true})
12
+ expect(described_class[:another_test]).to eq({something_else: false})
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrainstemAdaptor do
4
+ describe '.parser=' do
5
+ let(:parser) { double('real parser', parse: true) }
6
+
7
+ specify do
8
+ expect { described_class.parser = parser }.to change(described_class, :parser).to(parser)
9
+ end
10
+
11
+ specify do
12
+ expect { described_class.parser = double('something wrong') }.to raise_error ArgumentError
13
+ end
14
+ end
15
+
16
+ describe '.parser' do
17
+ it 'users JSON parser by default' do
18
+ expect { described_class.parser }.not_to raise_error
19
+ end
20
+ end
21
+
22
+ describe '.default_specification' do
23
+ specify do
24
+ expect { described_class.default_specification }.not_to raise_error
25
+ end
26
+ end
27
+
28
+ describe '.specification=' do
29
+ let(:specification) { {users: {}} }
30
+
31
+ specify do
32
+ expect { described_class.specification = specification }.to change(described_class, :default_specification).to(specification)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ require 'brainstem-adaptor'
2
+
3
+ RSpec.configure do |config|
4
+ config.mock_with :rspec
5
+ config.color_enabled = true
6
+ config.formatter = :documentation
7
+ end
8
+
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brainstem-adaptor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Sergei Zinin (einzige)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0
41
+ description: Parses Brainstem responses, makes it convenient to organize access to
42
+ your data.
43
+ email: szinin@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files:
47
+ - README.md
48
+ files:
49
+ - .gitignore
50
+ - .travis.yml
51
+ - Gemfile
52
+ - README.md
53
+ - Rakefile
54
+ - brainstem-adaptor.gemspec
55
+ - lib/brainstem-adaptor.rb
56
+ - lib/brainstem_adaptor/association.rb
57
+ - lib/brainstem_adaptor/invalid_response_error.rb
58
+ - lib/brainstem_adaptor/record.rb
59
+ - lib/brainstem_adaptor/response.rb
60
+ - lib/brainstem_adaptor/specification.rb
61
+ - spec/lib/brainstem_adaptor/association_spec.rb
62
+ - spec/lib/brainstem_adaptor/invalid_response_error_spec.rb
63
+ - spec/lib/brainstem_adaptor/record_spec.rb
64
+ - spec/lib/brainstem_adaptor/response_spec.rb
65
+ - spec/lib/brainstem_adaptor/specification_spec.rb
66
+ - spec/lib/brainstem_adaptor_spec.rb
67
+ - spec/spec_helper.rb
68
+ homepage: http://github.com/einzige/brainstem-adaptor
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.1.5
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Brainstem API Adaptor
92
+ test_files: []