baby_squeel 0.2.2 → 0.3.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: bc084f58970dcb50513671717f743e024a391355
4
- data.tar.gz: 1a4430236093f7554273c2a4357e0ad81879fe2d
3
+ metadata.gz: 7011dd272658bb35f29c2fee9bef07432c6fd6b8
4
+ data.tar.gz: 987fcf1f4682154eefb19e8aeb9e2671f48d04ea
5
5
  SHA512:
6
- metadata.gz: fc821c6eaca9be86e6559a2c354b47125a8a6c549e6b1b4064bc6354baa7ff6ecf186ebe66141b05681f77656834b787dbce705786f5e785e67fd4df3b859df1
7
- data.tar.gz: d9428e48fd5ce4b0d21b88c253616de212e7bda084e5af79888439f2f97180593757cb0a0fdf0efbdd3f952e97e9193412b201557580967802065cbf66b65d93
6
+ metadata.gz: 9f1b3d6e1f178ab428df6b1c23d7672ba2c53fa0f14814cf72c60c294e1236ba01240f875a3c5bbdde21d25976da72403786325f274bcd28020b0dfbaf474c41
7
+ data.tar.gz: 72efcf684fe78d5918914384d9f315b173adafa53af5044ce053c4b8ddb11a86067121c87997efd4254550d01064b825c68d5378b327e424dd6763bdc22e0c57
data/CHANGELOG.md CHANGED
@@ -1,8 +1,17 @@
1
1
  ## Unreleased
2
2
 
3
- Nothing yet!
3
+ Nothing yet.
4
4
 
5
- ## [0.2.2] - 2015-03-30
5
+ ## [0.3.0] - 2016-06-26
6
+ ### Added
7
+ - Added Squeel compatibility mode that allows `select`, `order`, `joins`, `group`, `where`, and `having` to accept DSL blocks.
8
+ - Added the ability to query tables that aren't backed by Active Record models.
9
+ - Added `BabySqueel::[]`, which provides a `BabySqueel::Relation` for models, or a `BabySqueel::Table` for symbols/strings.
10
+
11
+ ### Changed
12
+ - Renamed `BabySqueel::Association::AliasingError` to `BabySqueel::AssociationAliasingError`.
13
+
14
+ ## [0.2.2] - 2016-03-30
6
15
  ### Added
7
16
  - Support for `group` (`grouping`) and `having` (`when_having`).
8
17
  - Support for sifters.
@@ -14,14 +23,14 @@ Nothing yet!
14
23
  - Fix missing bind values When joining through associations with default scope.
15
24
  - Removed `ActiveRecord::VERSION` specific handling of the `WhereChain`.
16
25
 
17
- ## [0.2.1] - 2015-03-27
26
+ ## [0.2.1] - 2016-03-27
18
27
  ### Added
19
28
  - Support for subqueries.
20
29
 
21
30
  ### Fixed
22
31
  - Some Arel nodes did not have access to `as` expressions.
23
32
 
24
- ## [0.2.0] - 2015-03-25
33
+ ## [0.2.0] - 2016-03-25
25
34
  ### Added
26
35
  - References to aliased joins in a `select`, `where`, or `order` expression now use the aliased table name.
27
36
 
@@ -32,11 +41,12 @@ Nothing yet!
32
41
 
33
42
  - Associations referencing the same table weren't being aliased.
34
43
 
35
- ## [0.1.0] - 2015-03-16
44
+ ## [0.1.0] - 2016-03-16
36
45
  ### Added
37
46
  - Initial support for selects, orders, wheres, and joins.
38
47
 
39
- [Unreleased]: https://github.com/rzane/baby_squeel/compare/v0.2.2...HEAD
40
- [0.2.1]: https://github.com/rzane/baby_squeel/compare/v0.2.1...v0.2.2
48
+ [Unreleased]: https://github.com/rzane/baby_squeel/compare/v0.3.0...HEAD
49
+ [0.3.0]: https://github.com/rzane/baby_squeel/compare/v0.2.2...v0.3.0
50
+ [0.2.2]: https://github.com/rzane/baby_squeel/compare/v0.2.1...v0.2.2
41
51
  [0.2.1]: https://github.com/rzane/baby_squeel/compare/v0.2.0...v0.2.1
42
52
  [0.2.0]: https://github.com/rzane/baby_squeel/compare/v0.1.0...v0.2.0
data/README.md CHANGED
@@ -6,11 +6,9 @@
6
6
 
7
7
  <img align="right" src="http://static.thefrisky.com/uploads/2010/07/01/pig_in_boots_070110_m.jpg" alt="biddy piggy">
8
8
 
9
- Have you ever used the [squeel](https://github.com/activerecord-hackery/squeel) gem? It's a really nice way to build complex queries. However, squeel monkeypatches ActiveRecord internals, so it has a tendency to break every time a new ActiveRecord version comes out.
9
+ Have you ever used the [Squeel](https://github.com/activerecord-hackery/squeel) gem? It's a really nice way to build complex queries. However, Squeel monkeypatches Active Record internals, because it was aimed at enhancing the existing API with the aim of inclusion into Rails. However, that inclusion never happened, and it left Squeel susceptible to breakage from arbitrary changes in Active Record, eventually burning out the maintainer.
10
10
 
11
- For me, that's a deal breaker. BabySqueel provides a query DSL for ActiveRecord without all of the evil. :heart:
12
-
13
- It's really just a layer of sugar on top of Arel.
11
+ BabySqueel provides a Squeel-like query DSL for Active Record while hopefully avoiding the majority of the version upgrade difficulties via a minimum of monkeypatching. :heart:
14
12
 
15
13
  ## Installation
16
14
 
@@ -30,7 +28,7 @@ Or install it yourself as:
30
28
 
31
29
  ## Introduction
32
30
 
33
- With ActiveRecord, you might write something like this:
31
+ With Active Record, you might write something like this:
34
32
 
35
33
  ```ruby
36
34
  Post.where('created_at >= ?', 2.weeks.ago)
@@ -218,13 +216,25 @@ authors = Author.selecting { name.op('||', quoted('-dizzle')).as('swag') }
218
216
  authors.first.swag #=> 'Ray Zane-dizzle'
219
217
  ```
220
218
 
219
+ ##### Querying tables without Active Record models
220
+
221
+ ```ruby
222
+ table = BabySqueel[:some_table]
223
+
224
+ Post.joining {
225
+ table.on(table.post_id == id)
226
+ }.where.has {
227
+ table.some_column == 1
228
+ }
229
+ ```
230
+
221
231
  ## Sifters
222
232
 
223
- Sifters are like little snippets of conditions that take parameters.
233
+ Sifters are like little snippets of conditions that can take arguments.
224
234
 
225
235
  ```ruby
226
236
  class Post < ActiveRecord::Base
227
- sifter :funny do |str|
237
+ sifter :funny do
228
238
  title == 'rabies'
229
239
  end
230
240
  end
@@ -247,18 +257,31 @@ Post.joins(:author).where.has {
247
257
 
248
258
  The following methods give you access to BabySqueel's DSL:
249
259
 
250
- | BabySqueel | ActiveRecord Equivalent |
251
- |---------------|-------------------------|
252
- | `selecting` | `select` |
253
- | `ordering` | `order` |
254
- | `joining` | `joins` |
255
- | `grouping` | `group` |
256
- | `where.has` | `where` |
257
- | `when_having` | `having` |
260
+ | BabySqueel | Active Record Equivalent |
261
+ |---------------|--------------------------|
262
+ | `selecting` | `select` |
263
+ | `ordering` | `order` |
264
+ | `joining` | `joins` |
265
+ | `grouping` | `group` |
266
+ | `where.has` | `where` |
267
+ | `when_having` | `having` |
268
+
269
+ ## Compatibility Mode
270
+
271
+ If you want `select`, `order`, `joins`, `group`, and `having` to be able to accept DSL blocks, you can do so by adding the following to an initializer.
272
+
273
+ ```ruby
274
+ # config/initializers/baby_squeel.rb
275
+ BabySqueel.configure do |config|
276
+ config.enable_compat!
277
+ end
278
+ ```
279
+
280
+ Calling `enable_compat!` will override methods in Active Record, so use caution.
258
281
 
259
282
  ## Development
260
283
 
261
- 1. Pick an ActiveRecord version to develop against, then export it: `export AR=4.2.6`.
284
+ 1. Pick an Active Record version to develop against, then export it: `export AR=4.2.6`.
262
285
  2. Run `bin/setup` to install dependencies.
263
286
  3. Run `rake` to run the specs.
264
287
 
@@ -268,7 +291,6 @@ You can also run `bin/console` to open up a prompt where you'll have access to s
268
291
 
269
292
  Bug reports and pull requests are welcome on GitHub at https://github.com/rzane/baby_squeel.
270
293
 
271
-
272
294
  ## License
273
295
 
274
296
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/lib/baby_squeel.rb CHANGED
@@ -1,9 +1,28 @@
1
1
  require 'active_record'
2
2
  require 'active_record/relation'
3
3
  require 'baby_squeel/version'
4
+ require 'baby_squeel/errors'
4
5
  require 'baby_squeel/active_record'
5
6
 
6
7
  module BabySqueel
8
+ class << self
9
+ def configure
10
+ yield self
11
+ end
12
+
13
+ def enable_compatibility!
14
+ require 'baby_squeel/compat'
15
+ BabySqueel::Compat.enable!
16
+ end
17
+
18
+ def [](thing)
19
+ if thing.respond_to?(:model_name)
20
+ Relation.new(thing)
21
+ else
22
+ Table.new(Arel::Table.new(thing))
23
+ end
24
+ end
25
+ end
7
26
  end
8
27
 
9
28
  ::ActiveRecord::Base.extend BabySqueel::ActiveRecord::Sifting
@@ -1,18 +1,7 @@
1
- require 'baby_squeel/table'
1
+ require 'baby_squeel/relation'
2
2
 
3
3
  module BabySqueel
4
- class Association < Table
5
- class AliasingError < StandardError
6
- MESSAGE =
7
- 'Attempted to alias \'%{association}\' as \'%{alias_name}\', but the ' \
8
- 'association was implicitly joined. Either join the association ' \
9
- 'with `on` or remove the alias.'.freeze
10
-
11
- def initialize(association, alias_name)
12
- super format(MESSAGE, association: association, alias_name: alias_name)
13
- end
14
- end
15
-
4
+ class Association < Relation
16
5
  attr_reader :_reflection
17
6
 
18
7
  def initialize(parent, reflection)
@@ -37,7 +26,7 @@ module BabySqueel
37
26
  if _on
38
27
  super
39
28
  elsif _table.is_a? Arel::Nodes::TableAlias
40
- raise AliasingError.new(_reflection.name, _table.right)
29
+ raise AssociationAliasingError.new(_reflection.name, _table.right)
41
30
  else
42
31
  @parent._arel([self, *associations])
43
32
  end
@@ -0,0 +1,66 @@
1
+ module BabySqueel
2
+ module Compat
3
+ def self.enable!
4
+ ::ActiveRecord::Base.singleton_class.prepend QueryMethods
5
+ ::ActiveRecord::Relation.prepend QueryMethods
6
+ end
7
+
8
+ module QueryMethods
9
+ def joins(*args, &block)
10
+ if block_given? && args.empty?
11
+ joining(&block)
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ # Heads up, Array#select conflicts with
18
+ # ActiveRecord::QueryMethods#select. So, if arity
19
+ # is given to the block, we'll use Array#select.
20
+ # Otherwise, you'll be in a DSL block.
21
+ #
22
+ # Model.select { This is DSL }
23
+ # Model.select { |m| This is not DSL }
24
+ #
25
+ def select(*args, &block)
26
+ if block_given? && args.empty? && block.arity.zero?
27
+ selecting(&block)
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ def order(*args, &block)
34
+ if block_given? && args.empty?
35
+ ordering(&block)
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ def group(*args, &block)
42
+ if block_given? && args.empty?
43
+ grouping(&block)
44
+ else
45
+ super
46
+ end
47
+ end
48
+
49
+ def having(*args, &block)
50
+ if block_given? && args.empty?
51
+ when_having(&block)
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ def where(*args, &block)
58
+ if block_given? && args.empty?
59
+ super DSL.evaluate(self, &block)
60
+ else
61
+ super
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,9 +1,9 @@
1
1
  require 'baby_squeel/nodes'
2
- require 'baby_squeel/table'
2
+ require 'baby_squeel/relation'
3
3
  require 'baby_squeel/association'
4
4
 
5
5
  module BabySqueel
6
- class DSL < Table
6
+ class DSL < Relation
7
7
  class << self
8
8
  # Evaluates a block and unwraps the nodes
9
9
  def evaluate(scope, &block)
@@ -0,0 +1,24 @@
1
+ module BabySqueel
2
+ class NotFoundError < StandardError
3
+ def initialize(model_name, name)
4
+ super "There is no column or association named '#{name}' for #{model_name}."
5
+ end
6
+ end
7
+
8
+ class AssociationNotFoundError < StandardError
9
+ def initialize(model_name, name)
10
+ super "Association named '#{name}' was not found for #{model_name}."
11
+ end
12
+ end
13
+
14
+ class AssociationAliasingError < StandardError
15
+ MESSAGE =
16
+ 'Attempted to alias \'%{association}\' as \'%{alias_name}\', but the ' \
17
+ 'association was implicitly joined. Either join the association ' \
18
+ 'with `on` or remove the alias.'.freeze
19
+
20
+ def initialize(association, alias_name)
21
+ super format(MESSAGE, association: association, alias_name: alias_name)
22
+ end
23
+ end
24
+ end
@@ -9,14 +9,14 @@ module BabySqueel
9
9
 
10
10
  if ActiveRecord::VERSION::STRING < '4.1.0'
11
11
  def bind_values
12
+ return [] unless relation?
12
13
  _scope.joins(join_names(@associations)).bind_values
13
14
  end
14
15
  else
15
16
  def bind_values
16
- @bind_values ||= begin
17
- relation = _scope.joins(join_names(@associations))
18
- relation.arel.bind_values + relation.bind_values
19
- end
17
+ return [] unless relation?
18
+ relation = _scope.joins(join_names(@associations))
19
+ relation.arel.bind_values + relation.bind_values
20
20
  end
21
21
  end
22
22
 
@@ -37,6 +37,10 @@ module BabySqueel
37
37
 
38
38
  private
39
39
 
40
+ def relation?
41
+ @table.kind_of? BabySqueel::Relation
42
+ end
43
+
40
44
  def construct(associations, theirs, join_node)
41
45
  names = join_names associations
42
46
  mine = build names, join_node
@@ -94,10 +94,12 @@ module BabySqueel
94
94
  end
95
95
 
96
96
  def _arel
97
- parent_arel = @parent._arel._arel
97
+ parent_arel = @parent._arel
98
+ parent_arel &&= parent_arel._arel
99
+ parent_arel &&= parent_arel.last
98
100
 
99
- if parent_arel && parent_arel.last
100
- parent_arel.last.left[@name]
101
+ if parent_arel
102
+ parent_arel.left[@name]
101
103
  else
102
104
  super
103
105
  end
@@ -10,7 +10,7 @@ module BabySqueel
10
10
  #
11
11
  # ==== Example
12
12
  # BabySqueel::Nodes::Generic.arel_alias :unlike, :does_not_match
13
- # Post.where { title.unlike 'something' }
13
+ # Post.where.has { title.unlike 'something' }
14
14
  #
15
15
  def arel_alias(operator, arel_name)
16
16
  define_method operator do |other|
@@ -42,7 +42,7 @@ module BabySqueel
42
42
  # * +other+ - The argument to be passed to the SQL operator.
43
43
  #
44
44
  # ==== Example
45
- # Post.select { title.op('||', quoted('diddly')) }
45
+ # Post.selecting { title.op('||', quoted('diddly')) }
46
46
  # #=> SELECT "posts"."title" || 'diddly' FROM "posts"
47
47
  #
48
48
  def op(operator, other)
@@ -0,0 +1,42 @@
1
+ require 'baby_squeel/table'
2
+
3
+ module BabySqueel
4
+ class Relation < Table
5
+ attr_accessor :_scope
6
+
7
+ def initialize(scope)
8
+ super(scope.arel_table)
9
+ @_scope = scope
10
+ end
11
+
12
+ # Constructs a new BabySqueel::Association. Raises
13
+ # an exception if the association is not found.
14
+ def association(name)
15
+ if reflection = _scope.reflect_on_association(name)
16
+ Association.new(self, reflection)
17
+ else
18
+ raise AssociationNotFoundError.new(_table_name, name)
19
+ end
20
+ end
21
+
22
+ def sift(sifter_name, *args)
23
+ Nodes.wrap _scope.public_send("sift_#{sifter_name}", *args)
24
+ end
25
+
26
+ private
27
+
28
+ # @override BabySqueel::Table#_table_name
29
+ def _table_name
30
+ _scope.model_name
31
+ end
32
+
33
+ # @override BabySqueel::Table#resolve
34
+ def resolve(name)
35
+ if _scope.column_names.include?(name.to_s)
36
+ self[name]
37
+ elsif _scope.reflect_on_association(name)
38
+ association(name)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,24 +1,11 @@
1
1
  require 'baby_squeel/join_dependency'
2
2
 
3
3
  module BabySqueel
4
- class NotFoundError < StandardError
5
- def initialize(model_name, name)
6
- super "There is no column or association named '#{name}' for #{model_name}."
7
- end
8
- end
9
-
10
- class AssociationNotFoundError < StandardError
11
- def initialize(model_name, name)
12
- super "Association named '#{name}' was not found for #{model_name}."
13
- end
14
- end
15
-
16
4
  class Table
17
- attr_accessor :_scope, :_on, :_join, :_table
5
+ attr_accessor :_on, :_join, :_table
18
6
 
19
- def initialize(scope)
20
- @_scope = scope
21
- @_table = scope.arel_table
7
+ def initialize(arel_table)
8
+ @_table = arel_table
22
9
  @_join = Arel::Nodes::InnerJoin
23
10
  end
24
11
 
@@ -27,20 +14,6 @@ module BabySqueel
27
14
  Nodes::Attribute.new(self, key)
28
15
  end
29
16
 
30
- # Constructs a new BabySqueel::Association. Raises
31
- # an exception if the association is not found.
32
- def association(name)
33
- if reflection = _scope.reflect_on_association(name)
34
- Association.new(self, reflection)
35
- else
36
- raise AssociationNotFoundError.new(_scope.model_name, name)
37
- end
38
- end
39
-
40
- def sift(sifter_name, *args)
41
- Nodes.wrap _scope.public_send("sift_#{sifter_name}", *args)
42
- end
43
-
44
17
  # Alias a table. This is only possible when joining
45
18
  # an association explicitly.
46
19
  def alias(alias_name)
@@ -89,17 +62,18 @@ module BabySqueel
89
62
  # 2. Resolve the assocition's join clauses using ActiveRecord.
90
63
  #
91
64
  def _arel(associations = [])
65
+ return unless _on || associations.any?
92
66
  JoinDependency.new(self, associations)
93
67
  end
94
68
 
95
69
  private
96
70
 
71
+ def _table_name
72
+ arel_table.name
73
+ end
74
+
97
75
  def resolve(name)
98
- if _scope.column_names.include?(name.to_s)
99
- self[name]
100
- elsif _scope.reflect_on_association(name)
101
- association(name)
102
- end
76
+ self[name]
103
77
  end
104
78
 
105
79
  def respond_to_missing?(name, *)
@@ -110,7 +84,7 @@ module BabySqueel
110
84
  return super if !args.empty? || block_given?
111
85
 
112
86
  resolve(name) || begin
113
- raise NotFoundError.new(_scope.model_name, name)
87
+ raise NotFoundError.new(_table_name, name)
114
88
  end
115
89
  end
116
90
  end
@@ -1,3 +1,3 @@
1
1
  module BabySqueel
2
- VERSION = '0.2.2'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: baby_squeel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ray Zane
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-31 00:00:00.000000000 Z
11
+ date: 2016-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -94,10 +94,13 @@ files:
94
94
  - lib/baby_squeel.rb
95
95
  - lib/baby_squeel/active_record.rb
96
96
  - lib/baby_squeel/association.rb
97
+ - lib/baby_squeel/compat.rb
97
98
  - lib/baby_squeel/dsl.rb
99
+ - lib/baby_squeel/errors.rb
98
100
  - lib/baby_squeel/join_dependency.rb
99
101
  - lib/baby_squeel/nodes.rb
100
102
  - lib/baby_squeel/operators.rb
103
+ - lib/baby_squeel/relation.rb
101
104
  - lib/baby_squeel/table.rb
102
105
  - lib/baby_squeel/version.rb
103
106
  homepage: https://github.com/rzane/baby_squeel
@@ -120,9 +123,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
123
  version: '0'
121
124
  requirements: []
122
125
  rubyforge_project:
123
- rubygems_version: 2.4.8
126
+ rubygems_version: 2.5.1
124
127
  signing_key:
125
128
  specification_version: 4
126
129
  summary: A tiny squeel implementation without all of the evil.
127
130
  test_files: []
128
- has_rdoc: