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 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