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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e8db40497e5ea5d4b0be64e4f118fc6acdd798f
4
- data.tar.gz: 552b660a72a079f1ac2bf4b53cbb0639b472e8b0
3
+ metadata.gz: 1a678310855fbf171850f32bb38fad77e5e0855c
4
+ data.tar.gz: 9536ceacaa2b2d8d30f4275e30b1251e1ddca1eb
5
5
  SHA512:
6
- metadata.gz: beb3ebe622552e184725616d5b607f14cccaac5101984a179eac6a6a47d1934df36e193fe28e74ca9044678c9137174046c4760eaa1e89946f00c1c21ff485cb
7
- data.tar.gz: 9afb120cc06c7446187b982a9e81ea77a63822e158016bced2b37b326584bdc0b5227e2cf8f24de453d9ebad6c2d3caf296172a5d391edd50e68bfd1f6753898
6
+ metadata.gz: cafec340ad62336b249689666f495091dd528e7ee142e00275701d236018d3d98a1119e2b909b786d83353e2f14539c956759a5c9d5a9979b993634cad4b9312
7
+ data.tar.gz: 13c3f68f51a122364f6cbca51af918f70bd7c6e0ee4bb21bb5bd59f24807419cef2a55dce590d513a2319751aa0dfd4580f13d93dde78f64f16a8a3d6ba1c981
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Squirrell
2
2
 
3
- Squirrell is a completely non-magical library intended to make it easier to simplify your relationship with ActiveRecord.
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 |sqrrll|
37
- sqrrll.executor = -> (sql) { ActiveRecord::Base.connection.execute(sql) }
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(@id)
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(@pet))
100
- .where(wizards[:element].eq(@element))
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 = '#{@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 @name="Finn" @weapon="Grass Sword", etc...]
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 = #{@user_id} #{has_post?}
160
+ WHERE users.id = #{user_id} #{has_post?}
158
161
  SQL
159
162
  end
160
163
 
161
164
  def has_post?
162
- @post_id ? "AND posts.id = #{@post_id}" : ""
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.
@@ -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
- def raw_sql
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
- def klass.requires(*args)
41
- Squirrell.requires[self] = args
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
@@ -1,3 +1,3 @@
1
1
  module Squirrell
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -9,7 +9,6 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ['Matt Parsons']
10
10
  spec.email = ['parsonsmatt@gmail.com']
11
11
 
12
-
13
12
  spec.summary = 'A kinda magical gem for query objects'
14
13
  spec.homepage = 'https://www.github.com/parsonsmatt/squirrell'
15
14
  spec.license = 'MIT'
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.1.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-21 00:00:00.000000000 Z
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