brainstem-adaptor 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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: []