horza 0.0.1

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: 3b297b82ecba5dd4f72a6974c8018d2895f80dbc
4
+ data.tar.gz: c2403238d237160290cd7c68a37cda4f106b78b9
5
+ SHA512:
6
+ metadata.gz: 5b6cabd74b00d0d5954bd030d2854f1ed30feabb540dbdb4f53b46b79485f5b02cddc8f266b9230884de78b37fd241af2c59370ea278b7aa6d6db4714b6bffdb
7
+ data.tar.gz: 2a0fbb2d13b0e524642c66ddf7a37a0fb00b94f67c13d0f54621a159e779b4b235feabd910cb7b4c9082f661b3d2ebc54d11f61f4676efafa52b7985252d0d3b
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015 Blake Turner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,333 @@
1
+ # Get
2
+
3
+ Dynamically generate classes to encapsulate common database queries in Rails.
4
+
5
+ ## Why is this necessary?
6
+
7
+ #### Problem 1: Encapsulation
8
+
9
+ ORMs like ActiveRecord make querying the database incredible easy, but with power comes responsibility, and there's a lot of irresponsible code out there.
10
+
11
+ Consider:
12
+
13
+ ```ruby
14
+ User.where(name: 'blake').order('updated_at DESC').limit(2)
15
+ ```
16
+
17
+ This query is easy to read, and it works. Unfortunately, anything that uses it is tough to test, and any other implementation has to repeat this same cumbersome method chain.
18
+ Sure, you can wrap it in a method:
19
+
20
+ ```ruby
21
+ def find_two_blakes
22
+ User.where(name: 'blake').order('updated_at DESC').limit(2)
23
+ end
24
+ ```
25
+
26
+ But where does it live? Scope methods on models are (IMHO) hideous, so maybe a Helper? A Service? A private method in a class that I inherit from? The options aren't great.
27
+
28
+ #### Problem 2: Associations
29
+
30
+ ORMs like ActiveRecord also makes querying associations incredible easy. Consider:
31
+
32
+ ```html+ruby
33
+ <div>
34
+ <ul>
35
+ <% current_user.employer.sportscars.each do |car| %>
36
+ <li><%= car.cost %></li>
37
+ <% end >
38
+ </ul>
39
+ </div>
40
+ ```
41
+
42
+ The above is a great example of query pollution in the view layer. It's quick-to-build, tough-to-test, and very common in Rails.
43
+ A spec for a view like this would need to either create/stub each of the records with the proper associations, or stub the entire method chain.
44
+
45
+ If you move the query to the controller, it's a bit better:
46
+
47
+ ```ruby
48
+ # controller
49
+ def index
50
+ @employer = current_user.employer
51
+ @sportscars = @employer.sportscars
52
+ end
53
+ ```
54
+
55
+ ```html+ruby
56
+ #view
57
+ <div>
58
+ <ul>
59
+ <% @sportscars.each do |car| %>
60
+ <li><%= car.cost %></li>
61
+ <% end >
62
+ </ul>
63
+ </div>
64
+ ```
65
+
66
+ But that's just lipstick on a pig. We've simply shifted the testing burden to the controller; the dependencies and mocking complexity remain the same.
67
+
68
+ #### Problem 3: Self-Documenting code
69
+
70
+ Consider:
71
+
72
+ ```ruby
73
+ User.where(last_name: 'Turner').order('id DESC').limit(1)
74
+ ```
75
+
76
+ Most programmers familiar with Rails will be able to understand the above immediately, but only because they've written similar chains a hundred times.
77
+
78
+ ## Solution
79
+
80
+ The Get library tries to solve the above problems by dynamically generating classes that perform common database queries.
81
+ Get identifies four themes in common queries:
82
+
83
+ - **Singular**: Queries that expect a single record in response
84
+ - **Plural**: Queries that expect a collection of records in response
85
+ - **Query**: Query is performed on the given model
86
+ - **Association**: Query traverses the associations of the given model and returns a different model
87
+
88
+ These themes are not mutually exclusive; **Query** and **Association** can be either **Singular** or **Plural**.
89
+
90
+ ## Usage
91
+
92
+ #### Singular Queries - Return a single record
93
+
94
+ With field being queried in the class name
95
+ ```ruby
96
+ Get::UserById.run(123)
97
+ ```
98
+
99
+ Fail loudly
100
+ ```ruby
101
+ Get::UserById.run!(123)
102
+ ```
103
+
104
+ Slightly more flexible model:
105
+ ```ruby
106
+ Get::UserBy.run(id: 123, employer_id: 88)
107
+ ```
108
+
109
+ #### Plural Queries - Return a collection of records
110
+
111
+ _Note the plurality of 'Users'_
112
+ ```ruby
113
+ Get::UsersByLastName.run('Turner')
114
+ ```
115
+
116
+ #### Associations
117
+
118
+ Associations use 'From', and are sugar for the chains we so often write in rails.
119
+
120
+ _You can pass either an entity or an id, the only requirement is that it responds to #id_
121
+
122
+ Parent relationship (user.employer):
123
+ ```ruby
124
+ Get::EmployerFromUser.run(user)
125
+ ```
126
+
127
+ Child relationship (employer.users):
128
+ ```ruby
129
+ Get::UsersFromEmployer.run(employer_id)
130
+ ```
131
+
132
+ Complex relationship (user.employer.sportscars)
133
+ ```ruby
134
+ Get::SportscarsFromUser.run(user, via: :employer)
135
+ ```
136
+
137
+ Keep the plurality of associations in mind. If an Employer has many Users, UsersFromEmployer works,
138
+ but UserFromEmployer will throw `Get::Errors::InvalidAncestry`.
139
+
140
+ ## Entities
141
+
142
+ Ironically, one of the "features" of Get is its removal of the ability to query associations from the query response object.
143
+ This choice was made to combat query pollution throughout the app, particularly in the view layer.
144
+
145
+ To achieve this, Get returns **entities** instead of ORM objects (`ActiveRecord::Base`, etc.).
146
+ These entity classes are generated at runtime with names appropriate to their contents.
147
+ You can also register your own entities in the Get config.
148
+
149
+ ```ruby
150
+ >> result = Get::UserById.run(user.id)
151
+ >> result.class.name
152
+ >> "Get::Entities::GetUser"
153
+ ```
154
+
155
+ Individual entities will have all attributes accessible via dot notation and hash notation, but attempts to get associations will fail.
156
+ Collections have all of the common enumerator methods: `first`, `last`, `each`, and `[]`.
157
+
158
+ Dynamically generated Get::Entities are prefixed with `Get` to avoid colliding with your ORM objects.
159
+
160
+ ## Testing
161
+
162
+ A big motivation for this library is to make testing database queries easier.
163
+ Get accomplishes this by making class-level mocking/stubbing very easy.
164
+
165
+ Consider:
166
+
167
+ ```ruby
168
+ # sportscars_controller.rb
169
+
170
+ # ActiveRecord
171
+ def index
172
+ @sportscars = current_user.employer.sportscars
173
+ end
174
+
175
+ # Get
176
+ def index
177
+ @sportscars = Get::SportscarsFromUser.run(current_user, via: employer)
178
+ end
179
+ ```
180
+
181
+ The above methods do the exact same thing. Cool, let's test them:
182
+
183
+ ```ruby
184
+ # sportscars_controller.rb
185
+ describe SportscarsController, type: :controller do
186
+ context '#index' do
187
+ context 'ActiveRecord' do
188
+ let(:user) { FactoryGirl.build_stubbed(:user, employer: employer) }
189
+ let(:employer) { FactoryGirl.build_stubbed(:employer) }
190
+ let(:sportscars) { 3.times { FactoryGirl.build_stubbed(:sportscars) } }
191
+
192
+ before do
193
+ employer.sportscars << sportscars
194
+ sign_in(user)
195
+ get :index
196
+ end
197
+
198
+ it 'assigns sportscars' do
199
+ expect(assigns(:sportscars)).to eq(sportscars)
200
+ end
201
+ end
202
+
203
+ context 'Get' do
204
+ let(:user) { FactoryGirl.build_stubbed(:user, employer: employer) }
205
+ let(:sportscars) { 3.times { FactoryGirl.build_stubbed(:sportscars) } }
206
+
207
+ before do
208
+ allow(Get::SportscarsFromUser).to receive(:run).and_return(sportscars)
209
+ sign_in(user)
210
+ get :index
211
+ end
212
+
213
+ it 'assigns sportscars' do
214
+ expect(assigns(:sportscars)).to eq(sportscars)
215
+ end
216
+ end
217
+ end
218
+ end
219
+ ```
220
+
221
+ By encapsulating the query in a class, we're able to stub it at the class level, which eliminates then need to create any dependencies.
222
+ This will speed up tests (a little), but more importantly it makes them easier to read and write.
223
+
224
+ ## Config
225
+
226
+ **Define your adapter**
227
+
228
+ _config/initializers/ask.rb_
229
+ ```ruby
230
+ Get.configure { |config| config.adapter = :active_record }
231
+ ```
232
+
233
+ **Configure custom entities**
234
+
235
+ The code below will cause Get classes that begin with _Users_ (ie. `UsersByLastName`) to return a MyCustomEntity instead of the default `Get::Entities::User`.
236
+
237
+ _config/initializers/ask.rb_
238
+ ```ruby
239
+ class MyCustomEntity < Get::Entities::Collection
240
+ def east_london_length
241
+ "#{length}, bruv"
242
+ end
243
+ end
244
+
245
+ Get.config do |config|
246
+ config.register_entity(:users_by_last_name, MyCustomEntity)
247
+ end
248
+ ```
249
+
250
+ You can reset the config at any time using `Get.reset`.
251
+
252
+ ## Adapters
253
+
254
+ Get currently works with ActiveRecord.
255
+
256
+ ## Benchmarking
257
+
258
+ Get requests generally run < 1ms slower than ActiveRecord requests.
259
+
260
+ ```
261
+ GETTING BY ID, SAMPLE_SIZE: 400
262
+
263
+
264
+ >>> ActiveRecord
265
+ user system total real
266
+ Clients::User.find 0.170000 0.020000 0.190000 ( 0.224373)
267
+ Clients::User.find_by_id 0.240000 0.010000 0.250000 ( 0.342278)
268
+
269
+ >>> Get
270
+ user system total real
271
+ Get::UserById 0.300000 0.020000 0.320000 ( 0.402454)
272
+ Get::UserBy 0.260000 0.010000 0.270000 ( 0.350982)
273
+
274
+
275
+ GETTING SINGLE RECORD BY LAST NAME, SAMPLE_SIZE: 400
276
+
277
+
278
+ >>> ActiveRecord
279
+ user system total real
280
+ Clients::User.where 0.190000 0.020000 0.210000 ( 0.292516)
281
+ Clients::User.find_by_last_name 0.180000 0.010000 0.190000 ( 0.270033)
282
+
283
+ >>> Get
284
+ user system total real
285
+ Get::UserByLastName 0.240000 0.010000 0.250000 ( 0.337908)
286
+ Get::UserBy 0.310000 0.020000 0.330000 ( 0.415142)
287
+
288
+
289
+ GETTING MULTIPLE RECORDS BY LAST NAME, SAMPLE_SIZE: 400
290
+
291
+
292
+ >>> ActiveRecord
293
+ user system total real
294
+ Clients::User.where 0.020000 0.000000 0.020000 ( 0.012604)
295
+
296
+ >>> Get
297
+ user system total real
298
+ Get::UsersByLastName 0.100000 0.000000 0.100000 ( 0.105822)
299
+ Get::UsersBy 0.100000 0.010000 0.110000 ( 0.106406)
300
+
301
+
302
+ GETTING PARENT FROM CHILD, SAMPLE_SIZE: 400
303
+
304
+
305
+ >>> ActiveRecord
306
+ user system total real
307
+ Clients::User.find(:id).employer 0.440000 0.030000 0.470000 ( 0.580800)
308
+
309
+ >>> Get
310
+ user system total real
311
+ Get::EmployerFromUser 0.500000 0.020000 0.520000 ( 0.643316)
312
+
313
+
314
+ GETTING CHILDREN FROM PARENT, SAMPLE_SIZE: 400
315
+
316
+
317
+ >>> ActiveRecord
318
+ user system total real
319
+ Clients::Employer.find[:id].users 0.160000 0.020000 0.180000 ( 0.218710)
320
+
321
+ >>> Get
322
+ user system total real
323
+ Get::UsersFromEmployer 0.230000 0.010000 0.240000 ( 0.293037)
324
+
325
+
326
+ STATS
327
+
328
+ AVERAGE DIFF FOR BY ID: 0.000233s
329
+ AVERAGE DIFF FOR BY LAST NAME: 0.000238s
330
+ AVERAGE DIFF FOR BY LAST NAME (MULTIPLE): 0.000234s
331
+ AVERAGE DIFF FOR PARENT FROM CHILD: 0.000156s
332
+ AVERAGE DIFF FOR CHILDREN FROM PARENT: -0.000186s
333
+ ```
@@ -0,0 +1,67 @@
1
+ module Horza
2
+ module Adapters
3
+ class AbstractAdapter
4
+ attr_reader :context
5
+
6
+ class << self
7
+ def expected_errors
8
+ not_implemented_error
9
+ end
10
+
11
+ def context_for_entity(entity)
12
+ not_implemented_error
13
+ end
14
+
15
+ def entity_context_map
16
+ not_implemented_error
17
+ end
18
+
19
+ def not_implemented_error
20
+ raise ::Horza::Errors::MethodNotImplemented, 'You must implement this method in your adapter.'
21
+ end
22
+
23
+ def descendants
24
+ descendants = []
25
+ ObjectSpace.each_object(singleton_class) do |k|
26
+ descendants.unshift k unless k == self
27
+ end
28
+ descendants
29
+ end
30
+ end
31
+
32
+ def initialize(context)
33
+ @context = context
34
+ end
35
+
36
+ def get!(options = {})
37
+ not_implemented_error
38
+ end
39
+
40
+ def find_first(options = {})
41
+ not_implemented_error
42
+ end
43
+
44
+ def find_all(options = {})
45
+ not_implemented_error
46
+ end
47
+
48
+ def ancestors(options = {})
49
+ not_implemented_error
50
+ end
51
+
52
+ def eager_load(options = {})
53
+ not_implemented_error
54
+ end
55
+
56
+ def to_hash
57
+ not_implemented_error
58
+ end
59
+
60
+ private
61
+
62
+ def not_implemented_error
63
+ self.class.not_implemented_error
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,48 @@
1
+ module Horza
2
+ module Adapters
3
+ class ActiveRecord < AbstractAdapter
4
+ class << self
5
+ def expected_errors
6
+ [::ActiveRecord::RecordNotFound]
7
+ end
8
+
9
+ def context_for_entity(entity)
10
+ entity_context_map[entity]
11
+ end
12
+
13
+ def entity_context_map
14
+ @map ||= ::ActiveRecord::Base.descendants.reduce({}) { |hash, (klass)| hash.merge(klass.name.split('::').last.underscore.to_sym => klass) }
15
+ end
16
+ end
17
+
18
+ def get!(options = {})
19
+ @context = @context.find(options[:id])
20
+ end
21
+
22
+ def find_first(options = {})
23
+ @context = find_all(options).limit(1).first!
24
+ end
25
+
26
+ def find_all(options = {})
27
+ @context = @context.where(options[:conditions]).order('ID DESC')
28
+ end
29
+
30
+ def ancestors(options = {})
31
+ get!(options)
32
+ walk_family_tree(options)
33
+ rescue NoMethodError
34
+ raise ::Horza::Errors::InvalidAncestry.new('Invalid relation. Ensure that the plurality of your associations is correct.')
35
+ end
36
+
37
+ def to_hash
38
+ @context.attributes
39
+ end
40
+
41
+ private
42
+
43
+ def walk_family_tree(options)
44
+ options[:via].push(options[:result_key]).each { |relation| @context = @context.send(relation) }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,31 @@
1
+ module Horza
2
+ module Configuration
3
+ String.send(:include, ::Horza::CoreExtensions::String)
4
+
5
+ def configuration
6
+ @configuration ||= Config.new
7
+ end
8
+
9
+ def reset
10
+ @configuration = Config.new
11
+ @adapter, @adapter_map = nil, nil # Class-level cache clear
12
+ end
13
+
14
+ def configure
15
+ yield(configuration)
16
+ end
17
+
18
+ def adapter
19
+ raise ::Horza::Errors::AdapterNotConfigured.new unless configuration.adapter
20
+ @adapter ||= adapter_map[configuration.adapter]
21
+ end
22
+
23
+ def adapter_map
24
+ @adapter_map ||= ::Horza::Adapters::AbstractAdapter.descendants.reduce({}) { |hash, (klass)| hash.merge(klass.name.split('::').last.underscore.to_sym => klass) }
25
+ end
26
+ end
27
+
28
+ class Config
29
+ attr_accessor :adapter
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module Horza
2
+ module CoreExtensions
3
+ module String
4
+ def singular?
5
+ singularize(:en) == self
6
+ end
7
+
8
+ def plural?
9
+ pluralize(:en) == self
10
+ end
11
+
12
+ def symbolize
13
+ underscore.to_sym
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ module Horza
2
+ module Entities
3
+ class Collection
4
+ def initialize(collection)
5
+ @collection = collection
6
+ end
7
+
8
+ def each
9
+ @collection.each do |result|
10
+ yield singular_entity(result)
11
+ end
12
+ end
13
+
14
+ def [](index)
15
+ singular_entity(@collection[index])
16
+ end
17
+
18
+ private
19
+
20
+ def method_missing(method)
21
+ if [:length, :size, :empty?, :present?].include? method
22
+ @collection.send(method)
23
+ elsif [:first, :last].include? method
24
+ singular_entity(@collection.send(method))
25
+ end
26
+ end
27
+
28
+ def singular_entity(record)
29
+ singular_entity_class.new(Horza.adapter.new(record).to_hash)
30
+ end
31
+
32
+ # Collection classes have the form Horza::Entities::TypesMapper
33
+ # Single output requires the form Horza::Entities::TypeMapper
34
+ def singular_entity_class
35
+ @singular_entity ||= Kernel.const_get(self.class.name.deconstantize).const_get(self.class.name.demodulize.singularize)
36
+ rescue NameError
37
+ @singular_entity = ::Horza::Entities::Single
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,13 @@
1
+ require 'hashie'
2
+
3
+ module Horza
4
+ module Entities
5
+ class Single < Hash
6
+ include Hashie::Extensions::MethodAccess
7
+
8
+ def initialize(attributes)
9
+ update(attributes)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module Horza
2
+ module Entities
3
+ class << self
4
+ def const_missing(name)
5
+ parent_klass = name.to_s.plural? ? Horza::Entities::Collection : Horza::Entities::Single
6
+ Horza::Entities.const_set(name, Class.new(parent_klass))
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module Horza
2
+ module Errors
3
+ class AdapterNotConfigured < StandardError
4
+ end
5
+
6
+ class MethodNotImplemented < StandardError
7
+ end
8
+
9
+ class InvalidAncestry < StandardError
10
+ end
11
+ end
12
+ end
data/lib/horza.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'horza/adapters/abstract_adapter'
2
+ require 'horza/adapters/active_record'
3
+ require 'horza/core_extensions/string'
4
+ require 'horza/entities/single'
5
+ require 'horza/entities/collection'
6
+ require 'horza/entities'
7
+ require 'horza/configuration'
8
+ require 'horza/errors'
9
+ require 'active_support/inflector'
10
+
11
+ module Horza
12
+ extend Configuration
13
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Horza do
4
+ context '#adapter' do
5
+ context 'when adapter is not configured' do
6
+ it 'throws error' do
7
+ expect { Horza.adapter }.to raise_error(Horza::Errors::AdapterNotConfigured)
8
+ end
9
+ end
10
+
11
+ context 'when adapter is configured' do
12
+ before { Horza.configure { |config| config.adapter = :active_record } }
13
+ after { Horza.reset }
14
+ it 'returns appropriate class' do
15
+ expect(Horza.adapter).to eq Horza::Adapters::ActiveRecord
16
+ end
17
+ end
18
+ end
19
+
20
+ describe 'Entities' do
21
+ context '#const_missing' do
22
+ it 'dynamically defines classes' do
23
+ expect { Horza::Entities.const_get('NewClass') }.to_not raise_error
24
+ end
25
+ end
26
+
27
+ describe 'Collection' do
28
+ context '#singular_entity_class' do
29
+ context 'when singular entity class does not exist' do
30
+ module TestNamespace
31
+ class GetUsers < Horza::Entities::Collection
32
+ end
33
+ end
34
+
35
+ it 'returns Horza::Collection::Single' do
36
+ expect(TestNamespace::GetUsers.new([]).send(:singular_entity_class)).to eq Horza::Entities::Single
37
+ end
38
+ end
39
+
40
+ context 'when singular entity class exists' do
41
+ module TestNamespace
42
+ class GetEmployers < Horza::Entities::Collection
43
+ end
44
+
45
+ class GetEmployer < Horza::Entities::Single
46
+ end
47
+ end
48
+
49
+ it 'returns the existing singular class' do
50
+ expect(TestNamespace::GetEmployers.new([]).send(:singular_entity_class)).to eq TestNamespace::GetEmployer
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'byebug'
3
+ require 'rspec'
4
+ require 'bundler/setup'
5
+ Bundler.setup
6
+
7
+ require 'horza'
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: horza
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Blake Turner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hashie
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.4.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.4.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: 2.3.11
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.3.11
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 2.4.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.4.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '4.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '4.0'
69
+ description: Horza is a shapeshifter that provides common inputs and outputs for your
70
+ ORM
71
+ email: mail@blakewilliamturner.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - LICENSE.txt
77
+ - README.md
78
+ - lib/horza.rb
79
+ - lib/horza/adapters/abstract_adapter.rb
80
+ - lib/horza/adapters/active_record.rb
81
+ - lib/horza/configuration.rb
82
+ - lib/horza/core_extensions/string.rb
83
+ - lib/horza/entities.rb
84
+ - lib/horza/entities/collection.rb
85
+ - lib/horza/entities/single.rb
86
+ - lib/horza/errors.rb
87
+ - spec/horza_spec.rb
88
+ - spec/spec_helper.rb
89
+ homepage: https://github.com/onfido/horza
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.2.2
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Keep your app ORM-agnostic
113
+ test_files:
114
+ - spec/horza_spec.rb
115
+ - spec/spec_helper.rb
116
+ has_rdoc: