activerecord-refined 0.3.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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +130 -0
- data/Rakefile +8 -0
- data/activerecord-refined.gemspec +28 -0
- data/lib/active_record/refined/ast.rb +219 -0
- data/lib/active_record/refined.rb +138 -0
- data/lib/activerecord-refined/version.rb +5 -0
- data/lib/activerecord-refined.rb +7 -0
- data/test/test_block_syntax.rb +307 -0
- data/test/test_helper.rb +31 -0
- metadata +119 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4c6743021d5bc3d9aa3f3951ba64de77efcb51ed451aa338858d360ea1714b07
|
|
4
|
+
data.tar.gz: 3b2521ec7a2f0ccbbd68cf3e134461076a3ce77f3e7d545a9c27ba7d38860ca8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 344141c84a629959a125ba4a6513274faa548f56de4442ed5333ae5346fa15256c1020ba834ec9713ee0b366fd1dda1371ba211bd6bbfbf5f2a5b705ed52c429
|
|
7
|
+
data.tar.gz: 29f9d6ac4ad68e031960e297fe3c9d30a50cab8f288d3599deb0f5607198238e61badf2e0e961dc78bceac3c3306d7577e7caba4e1224adea279ac741a7c37f7
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2012 Akira Matsuda
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# ActiveRecord::Refined
|
|
2
|
+
|
|
3
|
+
Adding clean and powerful query syntax on ActiveRecord using refinements.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
Author.
|
|
7
|
+
joins(:posts) { :posts[:author_id] == :authors[:id] }.
|
|
8
|
+
where { (:authors[:age] == (20..40)) & (:posts[:published] == true) }
|
|
9
|
+
# SELECT "authors".* FROM "authors"
|
|
10
|
+
# INNER JOIN "posts" ON "posts"."author_id" = "authors"."id"
|
|
11
|
+
# WHERE "authors"."age" BETWEEN 20 AND 40 AND "posts"."published" = TRUE
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## History
|
|
15
|
+
|
|
16
|
+
This gem was formerly known as **activerecord-refinements**, created by Akira Matsuda
|
|
17
|
+
to experiment with the initial implementation of Ruby 2.0 Refinements. Because of the
|
|
18
|
+
Refinements' spec change, that implementation stopped working on Ruby 2.0.0 stable, and
|
|
19
|
+
the project was left dormant for a long time.
|
|
20
|
+
|
|
21
|
+
It has now been renamed to **activerecord-refined** and reimplemented on top of
|
|
22
|
+
`Proc#refined`, which will be introduced in Ruby 4.1. `Proc#refined` returns a new proc that
|
|
23
|
+
is evaluated with the given refinements activated, so a block written by the caller can
|
|
24
|
+
be re-interpreted under the query DSL's refinements:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
def evaluate_block(&block)
|
|
28
|
+
refined_block = block.refined(ActiveRecord::Refined::BlockSyntax)
|
|
29
|
+
BlockContext.new.instance_exec(&refined_block)
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This is exactly what the old implementation needed and could not do, so the query syntax
|
|
34
|
+
works again without monkey-patching `Symbol` globally.
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
* Ruby 4.1 or later (for `Proc#refined`; not released yet, so a `ruby-master` build is needed for now)
|
|
39
|
+
* ActiveRecord 7.0 or later
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
Add this line to your application's Gemfile:
|
|
44
|
+
|
|
45
|
+
gem 'activerecord-refined'
|
|
46
|
+
|
|
47
|
+
And then execute:
|
|
48
|
+
|
|
49
|
+
$ bundle
|
|
50
|
+
|
|
51
|
+
Or install it yourself as:
|
|
52
|
+
|
|
53
|
+
$ gem install activerecord-refined
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
Just require the gem, and `where`, `select`, `joins`, `left_outer_joins`, `having`,
|
|
58
|
+
`order` and `group` will accept a block.
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
require 'activerecord-refined'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Inside the block, symbols denote columns of the receiver's table, and `:table[:column]`
|
|
65
|
+
denotes a qualified column.
|
|
66
|
+
|
|
67
|
+
### Conditions
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
Author.where { :age >= 18 }
|
|
71
|
+
Author.where { :name =~ 'A%' } # LIKE
|
|
72
|
+
Author.where { :name !~ '%test%' } # NOT LIKE
|
|
73
|
+
Author.where { :age == (20..40) } # BETWEEN
|
|
74
|
+
Author.where { :country == %w[JP US] } # IN
|
|
75
|
+
Author.where { :country != %w[JP US] } # NOT IN
|
|
76
|
+
Author.where { :country.null? } # IS NULL
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Combine predicates with `&`, `|` and `!`. Ruby's operator precedence makes the
|
|
80
|
+
parentheses around each comparison necessary:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
Author.where { (:age >= 18) & ((:country == 'JP') | (:country == 'US')) }
|
|
84
|
+
Author.where { !((:age == (0..17)) | :country.null?) }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Joins
|
|
88
|
+
|
|
89
|
+
The block is the `ON` clause:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
Author.
|
|
93
|
+
joins(:posts) { :posts[:author_id] == :authors[:id] }.
|
|
94
|
+
joins(:comments) { :comments[:post_id] == :posts[:id] }
|
|
95
|
+
|
|
96
|
+
Author.left_outer_joins(:posts) { :posts[:author_id] == :authors[:id] }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Aggregates, functions and aliases
|
|
100
|
+
|
|
101
|
+
`count`, `sum`, `avg`, `min` and `max` are available as methods, as are the scalar
|
|
102
|
+
functions `upper`, `lower`, `length`, `trim`, `coalesce`, `abs` and `round`. Use `.as`
|
|
103
|
+
for a column alias, and `.asc` / `.desc` for the sort direction. Return an array to
|
|
104
|
+
select or order by multiple expressions.
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
Author.
|
|
108
|
+
joins(:posts) { :posts[:author_id] == :authors[:id] }.
|
|
109
|
+
where { :posts[:published] == true }.
|
|
110
|
+
group { :authors[:id] }.
|
|
111
|
+
having { count(:posts[:id]) > 1 }.
|
|
112
|
+
order { count(:posts[:id]).desc }.
|
|
113
|
+
select {
|
|
114
|
+
[
|
|
115
|
+
upper(:authors[:name]).as(:author),
|
|
116
|
+
count(:posts[:id]).as(:post_count),
|
|
117
|
+
avg(:posts[:likes]).as(:avg_likes),
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
See `examples/` for complete, runnable scripts.
|
|
123
|
+
|
|
124
|
+
## Contributing
|
|
125
|
+
|
|
126
|
+
1. Fork it
|
|
127
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
128
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
129
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
130
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'activerecord-refined/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "activerecord-refined"
|
|
8
|
+
gem.version = Activerecord::Refined::VERSION
|
|
9
|
+
gem.authors = ["Shugo Maeda"]
|
|
10
|
+
gem.email = ["shugo@ruby-lang.org"]
|
|
11
|
+
gem.description = 'Adding clean and powerful query syntax on AR using refinements'
|
|
12
|
+
gem.summary = 'ActiveRecord + Ruby 2.0 refinements'
|
|
13
|
+
gem.homepage = 'https://github.com/shugo/activerecord-refined'
|
|
14
|
+
|
|
15
|
+
gem.files = `git ls-files`.split($/)
|
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
gem.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
# Proc#refined is available since Ruby 4.1. 4.1.0.dev is required to allow
|
|
21
|
+
# ruby-master builds, which sort before the 4.1.0 release.
|
|
22
|
+
gem.required_ruby_version = '>= 4.1.0.dev'
|
|
23
|
+
|
|
24
|
+
gem.add_dependency 'activerecord', ['>= 7.0']
|
|
25
|
+
gem.add_development_dependency 'sqlite3', ['>= 0']
|
|
26
|
+
gem.add_development_dependency 'minitest', ['>= 0']
|
|
27
|
+
gem.add_development_dependency 'rake', ['>= 0']
|
|
28
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module Refined
|
|
3
|
+
module AST
|
|
4
|
+
class Node
|
|
5
|
+
def to_arel(table)
|
|
6
|
+
raise ScriptError, "subclass must override this method"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def as(alias_name)
|
|
10
|
+
As.new(self, alias_name)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def asc
|
|
14
|
+
Ordering.new(self, :asc)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def desc
|
|
18
|
+
Ordering.new(self, :desc)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Predicate < Node
|
|
23
|
+
def &(other)
|
|
24
|
+
And.new(self, other)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def |(other)
|
|
28
|
+
Or.new(self, other)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def !
|
|
32
|
+
Not.new(self)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Column < Node
|
|
37
|
+
attr_reader :table_name, :column_name
|
|
38
|
+
|
|
39
|
+
def initialize(table_name, column_name)
|
|
40
|
+
@table_name = table_name
|
|
41
|
+
@column_name = column_name
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_arel(_table)
|
|
45
|
+
Arel::Table.new(table_name)[column_name]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
%i[== != =~ !~ > >= < <=].each do |op|
|
|
49
|
+
define_method(op) {|val| Comparison.new(self, op, val) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def null?
|
|
53
|
+
Comparison.new(self, :==, nil)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
%i[count sum average maximum minimum].each do |func|
|
|
57
|
+
define_method(func) { Aggregate.new(self, func) }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class Aggregate < Node
|
|
62
|
+
attr_reader :operand, :function
|
|
63
|
+
|
|
64
|
+
def initialize(operand, function)
|
|
65
|
+
@operand = operand
|
|
66
|
+
@function = function
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_arel(table)
|
|
70
|
+
arel_operand = case operand
|
|
71
|
+
when Node then operand.to_arel(table)
|
|
72
|
+
else table[operand]
|
|
73
|
+
end
|
|
74
|
+
arel_operand.public_send(function)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
%i[== != =~ !~ > >= < <=].each do |op|
|
|
78
|
+
define_method(op) {|val| Comparison.new(self, op, val) }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class As < Node
|
|
83
|
+
attr_reader :operand, :alias_name
|
|
84
|
+
|
|
85
|
+
def initialize(operand, alias_name)
|
|
86
|
+
@operand = operand
|
|
87
|
+
@alias_name = alias_name
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def to_arel(table)
|
|
91
|
+
arel_operand = case operand
|
|
92
|
+
when Node then operand.to_arel(table)
|
|
93
|
+
when Symbol then table[operand]
|
|
94
|
+
else operand
|
|
95
|
+
end
|
|
96
|
+
arel_operand.as(alias_name.to_s)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class Ordering < Node
|
|
101
|
+
attr_reader :operand, :direction
|
|
102
|
+
|
|
103
|
+
def initialize(operand, direction)
|
|
104
|
+
@operand = operand
|
|
105
|
+
@direction = direction
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def to_arel(table)
|
|
109
|
+
arel_operand = case operand
|
|
110
|
+
when Node then operand.to_arel(table)
|
|
111
|
+
when Symbol then table[operand]
|
|
112
|
+
else operand
|
|
113
|
+
end
|
|
114
|
+
arel_operand.public_send(direction)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class Function < Node
|
|
119
|
+
attr_reader :name, :args
|
|
120
|
+
|
|
121
|
+
def initialize(name, args)
|
|
122
|
+
@name = name
|
|
123
|
+
@args = args
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def to_arel(table)
|
|
127
|
+
arel_args = args.map do |arg|
|
|
128
|
+
case arg
|
|
129
|
+
when Node then arg.to_arel(table)
|
|
130
|
+
when Symbol then table[arg]
|
|
131
|
+
else Arel::Nodes.build_quoted(arg)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
Arel::Nodes::NamedFunction.new(name, arel_args)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
%i[== != =~ !~ > >= < <=].each do |op|
|
|
138
|
+
define_method(op) {|val| Comparison.new(self, op, val) }
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class Comparison < Predicate
|
|
143
|
+
OPERATOR_MAP = {
|
|
144
|
+
:== => :eq, :!= => :not_eq, :=~ => :matches, :!~ => :does_not_match,
|
|
145
|
+
:> => :gt, :>= => :gteq, :< => :lt, :<= => :lteq
|
|
146
|
+
}.freeze
|
|
147
|
+
|
|
148
|
+
attr_reader :column, :operator, :value
|
|
149
|
+
|
|
150
|
+
def initialize(column, operator, value)
|
|
151
|
+
@column = column
|
|
152
|
+
@operator = operator
|
|
153
|
+
@value = value
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def to_arel(table)
|
|
157
|
+
arel_column = case column
|
|
158
|
+
when Node then column.to_arel(table)
|
|
159
|
+
else table[column]
|
|
160
|
+
end
|
|
161
|
+
arel_value = case value
|
|
162
|
+
when Node then value.to_arel(table)
|
|
163
|
+
else value
|
|
164
|
+
end
|
|
165
|
+
case
|
|
166
|
+
when operator == :== && Range === value
|
|
167
|
+
arel_column.between(value)
|
|
168
|
+
when operator == :!= && Range === value
|
|
169
|
+
arel_column.not_between(value)
|
|
170
|
+
when operator == :== && Array === value
|
|
171
|
+
arel_column.in(value)
|
|
172
|
+
when operator == :!= && Array === value
|
|
173
|
+
arel_column.not_in(value)
|
|
174
|
+
else
|
|
175
|
+
arel_column.public_send(OPERATOR_MAP.fetch(operator), arel_value)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
class And < Predicate
|
|
181
|
+
attr_reader :left, :right
|
|
182
|
+
|
|
183
|
+
def initialize(left, right)
|
|
184
|
+
@left = left
|
|
185
|
+
@right = right
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def to_arel(table)
|
|
189
|
+
left.to_arel(table).and(right.to_arel(table))
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class Or < Predicate
|
|
194
|
+
attr_reader :left, :right
|
|
195
|
+
|
|
196
|
+
def initialize(left, right)
|
|
197
|
+
@left = left
|
|
198
|
+
@right = right
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def to_arel(table)
|
|
202
|
+
left.to_arel(table).or(right.to_arel(table))
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
class Not < Predicate
|
|
207
|
+
attr_reader :operand
|
|
208
|
+
|
|
209
|
+
def initialize(operand)
|
|
210
|
+
@operand = operand
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def to_arel(table)
|
|
214
|
+
Arel::Nodes::Not.new(operand.to_arel(table))
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module Refined
|
|
3
|
+
module BlockSyntax
|
|
4
|
+
refine Symbol do
|
|
5
|
+
%i[== != =~ !~ > >= < <=].each do |op|
|
|
6
|
+
define_method(op) {|val| AST::Comparison.new(self, op, val) }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def null?
|
|
10
|
+
AST::Comparison.new(self, :==, nil)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
%i[count sum average maximum minimum].each do |func|
|
|
14
|
+
define_method(func) { AST::Aggregate.new(self, func) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def as(alias_name)
|
|
18
|
+
AST::As.new(self, alias_name)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def asc
|
|
22
|
+
AST::Ordering.new(self, :asc)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def desc
|
|
26
|
+
AST::Ordering.new(self, :desc)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def [](column_name)
|
|
30
|
+
AST::Column.new(self, column_name)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class BlockContext
|
|
36
|
+
AGGREGATE_FUNCTIONS = {
|
|
37
|
+
count: :count, sum: :sum, avg: :average, min: :minimum, max: :maximum,
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
40
|
+
AGGREGATE_FUNCTIONS.each do |name, arel_func|
|
|
41
|
+
define_method(name) {|column| AST::Aggregate.new(column, arel_func) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
SCALAR_FUNCTIONS = %i[upper lower length trim coalesce abs round].freeze
|
|
45
|
+
|
|
46
|
+
SCALAR_FUNCTIONS.each do |name|
|
|
47
|
+
define_method(name) {|*args| AST::Function.new(name.to_s.upcase, args) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module QueryMethods
|
|
52
|
+
def where(opts = nil, *rest, &block)
|
|
53
|
+
if block
|
|
54
|
+
super(evaluate_block(&block).to_arel(table))
|
|
55
|
+
else
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def select(*fields, &block)
|
|
61
|
+
if block
|
|
62
|
+
result = evaluate_block(&block)
|
|
63
|
+
arel = Array(result).map {|node| to_arel_field(node) }
|
|
64
|
+
super(*arel, &nil)
|
|
65
|
+
else
|
|
66
|
+
super
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def having(opts = nil, *rest, &block)
|
|
71
|
+
if block
|
|
72
|
+
super(evaluate_block(&block).to_arel(table))
|
|
73
|
+
else
|
|
74
|
+
super
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def order(*args, &block)
|
|
79
|
+
if block
|
|
80
|
+
result = evaluate_block(&block)
|
|
81
|
+
arel = Array(result).map {|node| to_arel_field(node) }
|
|
82
|
+
super(*arel, &nil)
|
|
83
|
+
else
|
|
84
|
+
super
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def group(*args, &block)
|
|
89
|
+
if block
|
|
90
|
+
result = evaluate_block(&block)
|
|
91
|
+
arel = Array(result).map {|node| to_arel_field(node) }
|
|
92
|
+
super(*arel, &nil)
|
|
93
|
+
else
|
|
94
|
+
super
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def joins(*args, &block)
|
|
99
|
+
if block
|
|
100
|
+
super(build_join_node(args.first, Arel::Nodes::InnerJoin, &block))
|
|
101
|
+
else
|
|
102
|
+
super
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def left_outer_joins(*args, &block)
|
|
107
|
+
if block
|
|
108
|
+
joins(build_join_node(args.first, Arel::Nodes::OuterJoin, &block))
|
|
109
|
+
else
|
|
110
|
+
super
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def evaluate_block(&block)
|
|
117
|
+
refined_block = block.refined(ActiveRecord::Refined::BlockSyntax)
|
|
118
|
+
BlockContext.new.instance_exec(&refined_block)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def to_arel_field(node)
|
|
122
|
+
case node
|
|
123
|
+
when AST::Node then node.to_arel(table)
|
|
124
|
+
when Symbol then table[node]
|
|
125
|
+
else node
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def build_join_node(target_table, join_class, &block)
|
|
130
|
+
ast = evaluate_block(&block)
|
|
131
|
+
join_class.new(
|
|
132
|
+
Arel::Table.new(target_table),
|
|
133
|
+
Arel::Nodes::On.new(ast.to_arel(table))
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
require_relative 'test_helper'
|
|
2
|
+
|
|
3
|
+
class TestBlockSyntax < Minitest::Test
|
|
4
|
+
def test_equal
|
|
5
|
+
assert_match(/WHERE "users"."name" = 'matz'/, User.where { :name == 'matz' }.to_sql)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_not_equal
|
|
9
|
+
assert_match(/WHERE "users"."name" != 'nobu'/, User.where { :name != 'nobu' }.to_sql)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_greater_than
|
|
13
|
+
assert_match(/WHERE "users"."age" > 3/, User.where { :age > 3 }.to_sql)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_greater_than_or_equal
|
|
17
|
+
assert_match(/WHERE "users"."age" >= 18/, User.where { :age >= 18 }.to_sql)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_less_than
|
|
21
|
+
assert_match(/WHERE "users"."age" < 60/, User.where { :age < 60 }.to_sql)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_less_than_or_equal
|
|
25
|
+
assert_match(/WHERE "users"."age" <= 35/, User.where { :age <= 35 }.to_sql)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_like
|
|
29
|
+
assert_match(/WHERE "users"."name" LIKE 'tender%'/, User.where { :name =~ 'tender%' }.to_sql)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_outside_of_where_block
|
|
33
|
+
assert_raises(ArgumentError) { :omg > 1 }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_and
|
|
37
|
+
assert_match(/WHERE "users"."name" = 'matz' AND "users"."age" > 18/,
|
|
38
|
+
User.where { (:name == 'matz') & (:age > 18) }.to_sql)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_or
|
|
42
|
+
assert_match(/WHERE \(?\"users\".\"name\" = 'matz' OR \"users\".\"name\" = 'nobu'\)?/,
|
|
43
|
+
User.where { (:name == 'matz') | (:name == 'nobu') }.to_sql)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_not
|
|
47
|
+
assert_match(/WHERE NOT \(?\"users\".\"name\" = 'matz'\)?/,
|
|
48
|
+
User.where { !(:name == 'matz') }.to_sql)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_complex_combination
|
|
52
|
+
sql = User.where { ((:name == 'matz') & (:age > 18)) | !(:name == 'nobu') }.to_sql
|
|
53
|
+
assert_match(/"users"."name" = 'matz'/, sql)
|
|
54
|
+
assert_match(/"users"."age" > 18/, sql)
|
|
55
|
+
assert_match(/NOT/, sql)
|
|
56
|
+
assert_match(/OR/, sql)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_between
|
|
60
|
+
assert_match(/WHERE "users"."age" BETWEEN 18 AND 65/,
|
|
61
|
+
User.where { :age == (18..65) }.to_sql)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_not_like
|
|
65
|
+
assert_match(/WHERE "users"."name" NOT LIKE 'tender%'/,
|
|
66
|
+
User.where { :name !~ 'tender%' }.to_sql)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_not_between
|
|
70
|
+
# Arel expands a bounded NOT BETWEEN into an OR of comparisons
|
|
71
|
+
assert_match(/WHERE \("users"."age" < 18 OR "users"."age" > 65\)/,
|
|
72
|
+
User.where { :age != (18..65) }.to_sql)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_is_null
|
|
76
|
+
assert_match(/WHERE "users"."name" IS NULL/,
|
|
77
|
+
User.where { :name.null? }.to_sql)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_is_null_qualified
|
|
81
|
+
assert_match(/WHERE "users"."name" IS NULL/,
|
|
82
|
+
User.where { :users[:name].null? }.to_sql)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_in
|
|
86
|
+
assert_match(/WHERE "users"."age" IN \(1, 2, 3\)/,
|
|
87
|
+
User.where { :age == [1, 2, 3] }.to_sql)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_not_in
|
|
91
|
+
assert_match(/WHERE "users"."age" NOT IN \(1, 2, 3\)/,
|
|
92
|
+
User.where { :age != [1, 2, 3] }.to_sql)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_qualified_column
|
|
96
|
+
assert_match(/WHERE "users"."name" = 'matz'/,
|
|
97
|
+
User.where { :users[:name] == 'matz' }.to_sql)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_column_to_column_comparison
|
|
101
|
+
sql = User.where { :users[:name] == :users[:age] }.to_sql
|
|
102
|
+
assert_match(/"users"."name" = "users"."age"/, sql)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_joins_with_block
|
|
106
|
+
sql = Author.joins(:posts) { :posts[:author_id] == :authors[:id] }.to_sql
|
|
107
|
+
assert_match(/INNER JOIN "posts" ON "posts"."author_id" = "authors"."id"/, sql)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_left_outer_joins_with_block
|
|
111
|
+
sql = Author.left_outer_joins(:posts) { :posts[:author_id] == :authors[:id] }.to_sql
|
|
112
|
+
assert_match(/LEFT OUTER JOIN "posts" ON "posts"."author_id" = "authors"."id"/, sql)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_select_aggregate
|
|
116
|
+
assert_match(/SELECT SUM\("users"."age"\)/,
|
|
117
|
+
User.select { :age.sum }.to_sql)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_select_aggregate_qualified
|
|
121
|
+
assert_match(/SELECT COUNT\("users"."id"\)/,
|
|
122
|
+
User.select { :users[:id].count }.to_sql)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_select_average
|
|
126
|
+
assert_match(/SELECT AVG\("users"."age"\)/,
|
|
127
|
+
User.select { :age.average }.to_sql)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def test_select_maximum
|
|
131
|
+
assert_match(/SELECT MAX\("users"."age"\)/,
|
|
132
|
+
User.select { :age.maximum }.to_sql)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_select_minimum
|
|
136
|
+
assert_match(/SELECT MIN\("users"."age"\)/,
|
|
137
|
+
User.select { :age.minimum }.to_sql)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_having_aggregate
|
|
141
|
+
sql = User.group(:name).having { :age.sum > 100 }.to_sql
|
|
142
|
+
assert_match(/GROUP BY "users"."name"/, sql)
|
|
143
|
+
assert_match(/HAVING SUM\("users"."age"\) > 100/, sql)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def test_select_avg_function
|
|
147
|
+
assert_match(/SELECT AVG\("users"."age"\)/,
|
|
148
|
+
User.select { avg(:age) }.to_sql)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def test_select_count_function
|
|
152
|
+
assert_match(/SELECT COUNT\("users"."id"\)/,
|
|
153
|
+
User.select { count(:id) }.to_sql)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_select_sum_function
|
|
157
|
+
assert_match(/SELECT SUM\("users"."age"\)/,
|
|
158
|
+
User.select { sum(:age) }.to_sql)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def test_select_min_function
|
|
162
|
+
assert_match(/SELECT MIN\("users"."age"\)/,
|
|
163
|
+
User.select { min(:age) }.to_sql)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def test_select_max_function
|
|
167
|
+
assert_match(/SELECT MAX\("users"."age"\)/,
|
|
168
|
+
User.select { max(:age) }.to_sql)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def test_function_qualified_column
|
|
172
|
+
assert_match(/SELECT AVG\("users"."age"\)/,
|
|
173
|
+
User.select { avg(:users[:age]) }.to_sql)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def test_having_function
|
|
177
|
+
sql = User.group(:name).having { sum(:age) > 100 }.to_sql
|
|
178
|
+
assert_match(/GROUP BY "users"."name"/, sql)
|
|
179
|
+
assert_match(/HAVING SUM\("users"."age"\) > 100/, sql)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def test_function_and_method_syntax_match
|
|
183
|
+
assert_equal User.select { :age.average }.to_sql,
|
|
184
|
+
User.select { avg(:age) }.to_sql
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def test_upper_function
|
|
188
|
+
assert_match(/SELECT UPPER\("users"."name"\)/,
|
|
189
|
+
User.select { upper(:name) }.to_sql)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def test_lower_function
|
|
193
|
+
assert_match(/SELECT LOWER\("users"."name"\)/,
|
|
194
|
+
User.select { lower(:name) }.to_sql)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def test_length_function_in_where
|
|
198
|
+
assert_match(/WHERE LENGTH\("users"."name"\) > 3/,
|
|
199
|
+
User.where { length(:name) > 3 }.to_sql)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def test_coalesce_function_with_literal
|
|
203
|
+
assert_match(/SELECT COALESCE\("users"."name", 'unknown'\)/,
|
|
204
|
+
User.select { coalesce(:name, 'unknown') }.to_sql)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def test_function_comparison
|
|
208
|
+
assert_match(/WHERE UPPER\("users"."name"\) = 'MATZ'/,
|
|
209
|
+
User.where { upper(:name) == 'MATZ' }.to_sql)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def test_nested_function
|
|
213
|
+
assert_match(/SELECT UPPER\(COALESCE\("users"."name", 'x'\)\)/,
|
|
214
|
+
User.select { upper(coalesce(:name, 'x')) }.to_sql)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def test_function_qualified_column_arg
|
|
218
|
+
assert_match(/SELECT UPPER\("users"."name"\)/,
|
|
219
|
+
User.select { upper(:users[:name]) }.to_sql)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def test_select_multiple_fields
|
|
223
|
+
assert_match(/SELECT UPPER\("users"."name"\), "users"."age"/,
|
|
224
|
+
User.select { [upper(:name), :age] }.to_sql)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def test_select_multiple_columns
|
|
228
|
+
assert_match(/SELECT "users"."name", "users"."age"/,
|
|
229
|
+
User.select { [:name, :age] }.to_sql)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def test_select_multiple_with_aggregate
|
|
233
|
+
assert_match(/SELECT "users"."name", SUM\("users"."age"\)/,
|
|
234
|
+
User.select { [:name, sum(:age)] }.to_sql)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def test_select_function_with_alias
|
|
238
|
+
assert_match(/SELECT UPPER\("users"."name"\) AS upper_name, "users"."age"/,
|
|
239
|
+
User.select { [upper(:name).as(:upper_name), :age] }.to_sql)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def test_select_column_alias
|
|
243
|
+
assert_match(/SELECT "users"."name" AS n/,
|
|
244
|
+
User.select { :name.as(:n) }.to_sql)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def test_select_qualified_column_alias
|
|
248
|
+
assert_match(/SELECT "users"."name" AS n/,
|
|
249
|
+
User.select { :users[:name].as(:n) }.to_sql)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def test_select_aggregate_alias
|
|
253
|
+
assert_match(/SELECT COUNT\("users"."id"\) AS cnt/,
|
|
254
|
+
User.select { count(:id).as(:cnt) }.to_sql)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def test_order_default_asc
|
|
258
|
+
assert_match(/ORDER BY "users"."age"/,
|
|
259
|
+
User.order { :age }.to_sql)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def test_order_desc
|
|
263
|
+
assert_match(/ORDER BY "users"."age" DESC/,
|
|
264
|
+
User.order { :age.desc }.to_sql)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def test_order_asc
|
|
268
|
+
assert_match(/ORDER BY "users"."age" ASC/,
|
|
269
|
+
User.order { :age.asc }.to_sql)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def test_order_multiple
|
|
273
|
+
assert_match(/ORDER BY "users"."age" DESC, "users"."name" ASC/,
|
|
274
|
+
User.order { [:age.desc, :name.asc] }.to_sql)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def test_order_qualified_column
|
|
278
|
+
assert_match(/ORDER BY "users"."name" DESC/,
|
|
279
|
+
User.order { :users[:name].desc }.to_sql)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def test_group_single
|
|
283
|
+
assert_match(/GROUP BY "users"."name"/,
|
|
284
|
+
User.group { :name }.to_sql)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def test_group_multiple
|
|
288
|
+
assert_match(/GROUP BY "users"."name", "users"."age"/,
|
|
289
|
+
User.group { [:name, :age] }.to_sql)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def test_group_qualified_column
|
|
293
|
+
assert_match(/GROUP BY "users"."name"/,
|
|
294
|
+
User.group { :users[:name] }.to_sql)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def test_group_with_having
|
|
298
|
+
sql = User.group { :name }.having { sum(:age) > 100 }.to_sql
|
|
299
|
+
assert_match(/GROUP BY "users"."name"/, sql)
|
|
300
|
+
assert_match(/HAVING SUM\("users"."age"\) > 100/, sql)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def test_default_where_syntax
|
|
304
|
+
assert_match(/WHERE "users"."name" = 'Ruby' AND "users"."age" = 19/,
|
|
305
|
+
User.where(name: 'Ruby', age: 19).to_sql)
|
|
306
|
+
end
|
|
307
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
3
|
+
|
|
4
|
+
require 'minitest/autorun'
|
|
5
|
+
Bundler.require
|
|
6
|
+
|
|
7
|
+
require 'active_record'
|
|
8
|
+
|
|
9
|
+
config = {:adapter => 'sqlite3', :database => ':memory:'}
|
|
10
|
+
ActiveRecord::Base.establish_connection(config)
|
|
11
|
+
|
|
12
|
+
class User < ActiveRecord::Base
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Author < ActiveRecord::Base
|
|
16
|
+
has_many :posts
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Post < ActiveRecord::Base
|
|
20
|
+
belongs_to :author
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class CreateAllTables < ActiveRecord::Migration[8.1]
|
|
24
|
+
def up
|
|
25
|
+
create_table(:users) {|t| t.string :name; t.integer :age}
|
|
26
|
+
create_table(:authors) {|t| t.string :name}
|
|
27
|
+
create_table(:posts) {|t| t.string :title; t.integer :author_id}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
ActiveRecord::Migration.verbose = false
|
|
31
|
+
CreateAllTables.new.up
|
metadata
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: activerecord-refined
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Shugo Maeda
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
-
|
|
17
|
+
- ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: "7.0"
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
-
|
|
25
|
+
- ">="
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: "7.0"
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: sqlite3
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
-
|
|
33
|
+
- ">="
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: "0"
|
|
36
|
+
type: :development
|
|
37
|
+
prerelease: false
|
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
-
|
|
41
|
+
- ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: "0"
|
|
44
|
+
- !ruby/object:Gem::Dependency
|
|
45
|
+
name: minitest
|
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
-
|
|
49
|
+
- ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: "0"
|
|
52
|
+
type: :development
|
|
53
|
+
prerelease: false
|
|
54
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
-
|
|
57
|
+
- ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: "0"
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: rake
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
-
|
|
65
|
+
- ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: "0"
|
|
68
|
+
type: :development
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
-
|
|
73
|
+
- ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: "0"
|
|
76
|
+
description: Adding clean and powerful query syntax on AR using refinements
|
|
77
|
+
email:
|
|
78
|
+
- shugo@ruby-lang.org
|
|
79
|
+
executables: []
|
|
80
|
+
extensions: []
|
|
81
|
+
extra_rdoc_files: []
|
|
82
|
+
files:
|
|
83
|
+
- .gitignore
|
|
84
|
+
- Gemfile
|
|
85
|
+
- LICENSE.txt
|
|
86
|
+
- README.md
|
|
87
|
+
- Rakefile
|
|
88
|
+
- activerecord-refined.gemspec
|
|
89
|
+
- lib/active_record/refined.rb
|
|
90
|
+
- lib/active_record/refined/ast.rb
|
|
91
|
+
- lib/activerecord-refined.rb
|
|
92
|
+
- lib/activerecord-refined/version.rb
|
|
93
|
+
- test/test_block_syntax.rb
|
|
94
|
+
- test/test_helper.rb
|
|
95
|
+
homepage: "https://github.com/shugo/activerecord-refined"
|
|
96
|
+
licenses: []
|
|
97
|
+
metadata: {}
|
|
98
|
+
rdoc_options: []
|
|
99
|
+
require_paths:
|
|
100
|
+
- lib
|
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
-
|
|
104
|
+
- ">="
|
|
105
|
+
- !ruby/object:Gem::Version
|
|
106
|
+
version: 4.1.0.dev
|
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
-
|
|
110
|
+
- ">="
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: "0"
|
|
113
|
+
requirements: []
|
|
114
|
+
rubygems_version: 4.1.0.dev
|
|
115
|
+
specification_version: 4
|
|
116
|
+
summary: ActiveRecord + Ruby 2.0 refinements
|
|
117
|
+
test_files:
|
|
118
|
+
- test/test_block_syntax.rb
|
|
119
|
+
- test/test_helper.rb
|