squirrell 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +64 -11
- data/lib/squirrell.rb +15 -47
- data/lib/squirrell/class_methods.rb +32 -0
- data/lib/squirrell/instance_methods.rb +43 -0
- data/lib/squirrell/version.rb +1 -1
- data/squirrell.gemspec +0 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a678310855fbf171850f32bb38fad77e5e0855c
|
4
|
+
data.tar.gz: 9536ceacaa2b2d8d30f4275e30b1251e1ddca1eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cafec340ad62336b249689666f495091dd528e7ee142e00275701d236018d3d98a1119e2b909b786d83353e2f14539c956759a5c9d5a9979b993634cad4b9312
|
7
|
+
data.tar.gz: 13c3f68f51a122364f6cbca51af918f70bd7c6e0ee4bb21bb5bd59f24807419cef2a55dce590d513a2319751aa0dfd4580f13d93dde78f64f16a8a3d6ba1c981
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Squirrell
|
2
2
|
|
3
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/squirrell.svg)](http://badge.fury.io/rb/squirrell)
|
4
|
+
|
5
|
+
Squirrell is a kinda magical library intended to make it easier to simplify your relationship with ActiveRecord.
|
4
6
|
ActiveRecord provides an immense amount of flexibility and power, and it's really easy to let this functionality become more-and-more intense.
|
5
7
|
Controllers doing arbitrary `where`s, other models doing a `find_by`, maybe even a hidden finder in the views somewhere.
|
6
8
|
This level of decoupling makes things difficult to test and obscures the lines in your application.
|
@@ -30,11 +32,11 @@ An executor responds to `:call`, accepts a single argument, and executes that ar
|
|
30
32
|
It doesn't really have to execute a SQL query, though.
|
31
33
|
It just probably should.
|
32
34
|
|
33
|
-
To configure Squirrell, you can do a block:
|
35
|
+
To configure Squirrell, you can do a block in an initializer somewhere:
|
34
36
|
|
35
37
|
```ruby
|
36
|
-
Squirrell.configure do |
|
37
|
-
|
38
|
+
Squirrell.configure do |sqrl|
|
39
|
+
sqrl.executor = -> (sql) { ActiveRecord::Base.connection.execute(sql) }
|
38
40
|
end
|
39
41
|
```
|
40
42
|
|
@@ -68,7 +70,7 @@ class UserFinder
|
|
68
70
|
requires :id
|
69
71
|
|
70
72
|
def finder
|
71
|
-
User.find(
|
73
|
+
User.find(id)
|
72
74
|
end
|
73
75
|
|
74
76
|
def process(result)
|
@@ -80,6 +82,7 @@ end
|
|
80
82
|
The `requires :id` line indicates what parameters must be passed to `find`.
|
81
83
|
An error will be raised if a required parameter is missing or if an extra parameter is passed.
|
82
84
|
The symbols in the hash are made into instance variables of the same name.
|
85
|
+
`attr_readers` are provided for them, so you can refer to them without `@`.
|
83
86
|
|
84
87
|
After the finding method gets called, `#process` gets called with the result of the query.
|
85
88
|
In the previous example, `result` would be an array, and it would convert the found users into a string wishing them a happy birthday.
|
@@ -96,8 +99,8 @@ class WizardByElementAndPet
|
|
96
99
|
|
97
100
|
def arel
|
98
101
|
wizards = Wizard.arel_table
|
99
|
-
wizards.where(wizards[:pet].eq(
|
100
|
-
.where(wizards[:element].eq(
|
102
|
+
wizards.where(wizards[:pet].eq(pet))
|
103
|
+
.where(wizards[:element].eq(element))
|
101
104
|
.project(wizards[:id])
|
102
105
|
end
|
103
106
|
end
|
@@ -115,7 +118,7 @@ class HeroByName
|
|
115
118
|
requires :name
|
116
119
|
|
117
120
|
def raw_sql
|
118
|
-
"SELECT heroes.id FROM heroes WHERE heroes.name = '#{
|
121
|
+
"SELECT heroes.id FROM heroes WHERE heroes.name = '#{name}'"
|
119
122
|
end
|
120
123
|
end
|
121
124
|
```
|
@@ -137,7 +140,7 @@ class HeroByName
|
|
137
140
|
end
|
138
141
|
|
139
142
|
HeroByName.find(name: "Finn")
|
140
|
-
# => [#<Hero:0x0987123
|
143
|
+
# => [#<Hero:0x0987123 name="Finn" weapon="Grass Sword", etc...]
|
141
144
|
```
|
142
145
|
|
143
146
|
Squirrell allows you to define optional permitted parameters:
|
@@ -154,12 +157,12 @@ def PermissionExample
|
|
154
157
|
SELECT *
|
155
158
|
FROM users
|
156
159
|
INNER JOIN posts ON users.id = posts.user_id
|
157
|
-
WHERE users.id = #{
|
160
|
+
WHERE users.id = #{user_id} #{has_post?}
|
158
161
|
SQL
|
159
162
|
end
|
160
163
|
|
161
164
|
def has_post?
|
162
|
-
|
165
|
+
post_id ? "AND posts.id = #{post_id}" : ""
|
163
166
|
end
|
164
167
|
end
|
165
168
|
```
|
@@ -176,6 +179,56 @@ Squirrell has a generator for queries.
|
|
176
179
|
* `--type=` can either be `raw_sql`, `finder`, or `arel`.
|
177
180
|
* The remaining elements are the required parameters for the query.
|
178
181
|
|
182
|
+
## Testing Squirrells
|
183
|
+
|
184
|
+
Mocking Squirrels is really easy. `expect(QueryObject).to receive(:find).and_return(results)` will capture the desired behavior in calling classes.
|
185
|
+
|
186
|
+
Testing Squirrells has two components: testing that the interaction with the database works as expected, and testing that the post-processing method works as expected.
|
187
|
+
|
188
|
+
For basic ActiveRecord finders, it's not really necessary to test `.find`.
|
189
|
+
ActiveRecord is well-tested and unlikely to be cause problems.
|
190
|
+
For Arel and raw SQL queries that have a bit more complexity, you'll likely want to actually touch the database in these tests.
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
describe HeroByName do
|
194
|
+
before :all do
|
195
|
+
let(:finn) { create(:hero, name: "Finn") }
|
196
|
+
let(:jake) { create(:hero, name: "Jake") }
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'finds by name' do
|
200
|
+
result = HeroByName.find(name: "Finn")
|
201
|
+
expect(result).to include(finn)
|
202
|
+
expect(result).to_not include(jake)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
You can gain access to the underlying Squirrell with `.new`, which lets you test the `process` method and any other methods you choose to define on the class.
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
describe MathQuery do
|
211
|
+
class MathQuery
|
212
|
+
include Squirrell
|
213
|
+
|
214
|
+
requires :math
|
215
|
+
|
216
|
+
def process(result)
|
217
|
+
result * 5
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
let(:subject) { MathQuery.new }
|
222
|
+
|
223
|
+
it 'multiples result by 5' do
|
224
|
+
expect(subject.process(5)).to eq(10)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
Generally, it'll be easiest to use and test the code if `process` is a pure function of it's input.
|
230
|
+
If you need to refer to those values, you can pass the parameters in to `new`: `MathQuery.new(math: "so cool")`.
|
231
|
+
|
179
232
|
## Development
|
180
233
|
|
181
234
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/squirrell.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'squirrell/version'
|
2
|
+
require 'squirrell/class_methods'
|
3
|
+
require 'squirrell/instance_methods'
|
2
4
|
|
5
|
+
# Including this module gives a few convenience methods for query objects.
|
3
6
|
module Squirrell
|
4
7
|
class << self
|
5
8
|
attr_accessor :requires
|
@@ -18,61 +21,26 @@ module Squirrell
|
|
18
21
|
yield self
|
19
22
|
end
|
20
23
|
|
21
|
-
|
22
|
-
sql = arel
|
23
|
-
fail InvalidArelError unless sql.respond_to? :to_sql
|
24
|
-
sql.to_sql
|
25
|
-
end
|
26
|
-
|
27
|
-
def process(results)
|
28
|
-
results
|
29
|
-
end
|
30
|
-
|
24
|
+
# Errors raised when the executor doesn't respond to call.
|
31
25
|
class ExecutorError < ArgumentError; end
|
26
|
+
|
27
|
+
# Error raised when result of `arel` doesn't respond to `to_sql`
|
32
28
|
class InvalidArelError < ArgumentError; end
|
33
29
|
|
30
|
+
# Error raised when a required parameter is not passed.
|
31
|
+
class MissingParameterError < ArgumentError; end
|
32
|
+
|
33
|
+
# Error raised when a parameter passed into `.find` is not included in either
|
34
|
+
# requires or permits.
|
35
|
+
class UnusedParameter < ArgumentError; end
|
36
|
+
|
34
37
|
def self.included(klass)
|
35
38
|
Squirrell.requires ||= {}
|
36
39
|
Squirrell.requires[klass] = []
|
37
40
|
Squirrell.permits ||= {}
|
38
41
|
Squirrell.permits[klass] = []
|
39
42
|
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
def klass.permits(*args)
|
45
|
-
Squirrell.permits[self] = args
|
46
|
-
end
|
47
|
-
|
48
|
-
def initialize(args)
|
49
|
-
Squirrell.requires[self.class].each do |k|
|
50
|
-
unless args.keys.include? k
|
51
|
-
fail ArgumentError, "Missing required parameter: #{k}"
|
52
|
-
end
|
53
|
-
instance_variable_set "@#{k}", args.delete(k)
|
54
|
-
end
|
55
|
-
|
56
|
-
Squirrell.permits[self.class].each do |k|
|
57
|
-
instance_variable_set "@#{k}", args.delete(k) if args.keys.include? k
|
58
|
-
end
|
59
|
-
|
60
|
-
fail ArgumentError if args.any?
|
61
|
-
end
|
62
|
-
|
63
|
-
def klass.find(args = {})
|
64
|
-
do_query(new(args))
|
65
|
-
end
|
66
|
-
|
67
|
-
def klass.do_query(object)
|
68
|
-
result = nil
|
69
|
-
if object.respond_to? :finder
|
70
|
-
result = object.finder
|
71
|
-
else
|
72
|
-
sql = object.raw_sql
|
73
|
-
result = Squirrell.executor.call(sql)
|
74
|
-
end
|
75
|
-
object.process(result)
|
76
|
-
end
|
43
|
+
klass.extend ClassMethods
|
44
|
+
klass.include InstanceMethods
|
77
45
|
end
|
78
46
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Squirrell
|
2
|
+
# Class methods for Squirrell objects.
|
3
|
+
module ClassMethods
|
4
|
+
def requires(*args)
|
5
|
+
Squirrell.requires[self] = args
|
6
|
+
define_readers args
|
7
|
+
end
|
8
|
+
|
9
|
+
def permits(*args)
|
10
|
+
Squirrell.permits[self] = args
|
11
|
+
define_readers args
|
12
|
+
end
|
13
|
+
|
14
|
+
def find(args = {})
|
15
|
+
do_query(new(args))
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def do_query(object)
|
21
|
+
result = object.finder || Squirrell.executor.call(object.raw_sql)
|
22
|
+
object.process(result)
|
23
|
+
end
|
24
|
+
|
25
|
+
def define_readers(args)
|
26
|
+
args.each do |arg|
|
27
|
+
attr_reader arg
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Squirrell
|
2
|
+
# Instance methods for Squirrell objects.
|
3
|
+
module InstanceMethods
|
4
|
+
# Override this method to do raw_sql.
|
5
|
+
def raw_sql
|
6
|
+
sql = arel
|
7
|
+
fail InvalidArelError unless sql.respond_to? :to_sql
|
8
|
+
sql.to_sql
|
9
|
+
end
|
10
|
+
|
11
|
+
# Override this method to do arel.
|
12
|
+
# Note: If you've overridden raw_sql, it won't work.
|
13
|
+
def arel
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Override this method to skip SQL execution.
|
18
|
+
def finder
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# Override this method to process results after query execution.
|
23
|
+
def process(results)
|
24
|
+
results
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(args = {})
|
28
|
+
return self if args.empty?
|
29
|
+
Squirrell.requires[self.class].each do |k|
|
30
|
+
unless args.keys.include? k
|
31
|
+
fail MissingParameterError, "Missing required parameter: #{k}"
|
32
|
+
end
|
33
|
+
instance_variable_set "@#{k}", args.delete(k)
|
34
|
+
end
|
35
|
+
|
36
|
+
Squirrell.permits[self.class].each do |k|
|
37
|
+
instance_variable_set "@#{k}", args.delete(k) if args.keys.include? k
|
38
|
+
end
|
39
|
+
|
40
|
+
fail UnusedParameter, "Unspecified parameters: #{args}" if args.any?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/squirrell/version.rb
CHANGED
data/squirrell.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: squirrell
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Parsons
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -120,6 +120,8 @@ files:
|
|
120
120
|
- lib/generators/sqrl/query/query_generator.rb
|
121
121
|
- lib/generators/sqrl/query/templates/query_template.rb.erb
|
122
122
|
- lib/squirrell.rb
|
123
|
+
- lib/squirrell/class_methods.rb
|
124
|
+
- lib/squirrell/instance_methods.rb
|
123
125
|
- lib/squirrell/version.rb
|
124
126
|
- squirrell.gemspec
|
125
127
|
homepage: https://www.github.com/parsonsmatt/squirrell
|