squirrell 0.1.0 → 0.2.0
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 +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
|
+
[](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
|