active_record_query 0.1.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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ar_query.gemspec
4
+ gemspec
@@ -0,0 +1,150 @@
1
+ # Active Record Query Plugin #
2
+
3
+ Imaginatively named, I know.
4
+
5
+ This is a proof-of-concept I want to be considered for inclusion in
6
+ Rails 4.
7
+
8
+ ## Synopsis ##
9
+
10
+ ``` ruby
11
+ Person.where { |q| q.name == 'Jon' && q.age == 22 }
12
+ # => SELECT * FROM people WHERE people.name = 'Jon' AND people.age = 22
13
+
14
+ Person.any { |q| q.name == 'Jon' || q.age == 22 }
15
+ # => SELECT * FROM people WHERE people.name = 'Jon' OR people.age = 22
16
+
17
+ Person.joins(:projects).where { |q| q.projects.name == 'Rails' }
18
+ # => SELECT * FROM people INNER JOIN projects WHERE projecs.name = 'Rails'
19
+
20
+ Person.any { |q| q.name = 'Jon' || q.and { q.age >= 10 && q.age < 30 } }
21
+ # => SELECT * FROM people WHERE people.name = 'Jon' OR (people.age >= 10 AND people.age < 30)
22
+ ```
23
+
24
+ ## Why? ##
25
+
26
+ * Makes people (who use it) less vulnerable to accidentally introducing
27
+ SQL injection points
28
+
29
+ * If we have the AST, we can draw inferences from it in Active Record.
30
+ For example, in Rails 4,
31
+
32
+ ``` ruby
33
+ Post.includes(:comments).where('comments.created_at > x')
34
+ ```
35
+
36
+ will no longer `JOIN` the `comments` table. You have to do:
37
+
38
+ ``` ruby
39
+ Post.includes(:comments).where('comments.created_at > x').references(:comments)
40
+ ```
41
+
42
+ With the AST available to us, we can automatically infer that
43
+ `comments` is referenced.
44
+
45
+ ## Prior art ##
46
+
47
+ * https://github.com/ernie/squeel
48
+ * http://defunkt.io/ambition/
49
+
50
+ ## Design goals ##
51
+
52
+ * Don't use `instance_eval`. Here be dragons.
53
+
54
+ * Make the syntax as easy on the eyes as possible.
55
+
56
+ `&&` and `||`
57
+ cannot be redefined as methods (more about that below), but `&` and
58
+ `|` can be. However, they bind tighter than comparison operators,
59
+ resulting in lots of unpleasant parentheses:
60
+
61
+ ``` ruby
62
+ Person.where { |q| (q.name == 'Jon') & (q.age == 22) }
63
+ ```
64
+
65
+ `&` and `|` are also commonly used for set operations, which have an
66
+ existing meaning in SQL.
67
+
68
+ ## Implementation ##
69
+
70
+ The `q` object is *mutable*.
71
+
72
+ Writing,
73
+
74
+ ``` ruby
75
+ Post.where { |q| q.name == 'Jon' && q.age == 22 }
76
+ ```
77
+
78
+ has the identical effect as writing,
79
+
80
+ ``` ruby
81
+ Post.where do |q|
82
+ q.name == 'Jon'
83
+ q.age == 22
84
+ end
85
+ ```
86
+
87
+ The `==` method return `true` to prevent the `&&` operator from
88
+ short-circuiting. Using `&&` is purely syntactical sugar.
89
+
90
+ Note that,
91
+
92
+ ``` ruby
93
+ Post.where { |q| q.name == 'Jon' || q.age == 22 }
94
+ ```
95
+
96
+ *would* short-circuit.
97
+
98
+ So whilst this wouldn't throw an error, it at
99
+ least would result in something that the user would (hopefully) notice
100
+ is wrong quite quickly (because `age` would be missing from the query
101
+ entirely).
102
+
103
+ This 'hack' is definitely the worst thing about the idea, but I think
104
+ that with adaquate documentation it wouldn't pose too much problem, and
105
+ it reads quite naturally.
106
+
107
+ ## Supported operators ##
108
+
109
+ ``` ruby
110
+ q.name == 'Jon'
111
+ q.name != 'Jon'
112
+ q.name =~ 'J%'
113
+ q.name !~ 'J%'
114
+ q.name > 22
115
+ q.name < 22
116
+ q.name >= 22
117
+ q.name <= 22
118
+ q.name.in ['Jon', 'Emily']
119
+ q.name.not_in ['Jon', 'Emily']
120
+ ```
121
+
122
+ ## Possible alternative syntaxes ##
123
+
124
+ ### Option 1 (Squeel syntax) ###
125
+
126
+ ``` ruby
127
+ Person.where { |q| (q.name == 'Jon') | (q.age == 22) }
128
+ ```
129
+
130
+ * Doesn't require the `q.and { ... }` thing for `AND`-within-`OR` or
131
+ `OR`-within-`AND`
132
+ * Possibly confusing use of set operators
133
+ * Lots of parentheses
134
+
135
+ ### Option 2 ###
136
+
137
+ ``` ruby
138
+ Person.where { |q| q.name == 'Jon'; q.age == 22 }
139
+ ```
140
+
141
+ * This works already, it's a question of what we advocate / document.
142
+
143
+ ### Option 3 ###
144
+
145
+ ``` ruby
146
+ Person.any { |q| q[:name] == 'Jon' || q.projects[:name] == 'Rails' }
147
+ ```
148
+
149
+ * Draws a clearer distinction between table and column names
150
+ * A bit more noisy
@@ -0,0 +1,19 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ namespace :test do
5
+ Rake::TestTask.new(:unit) do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/unit/**/*_test.rb']
8
+ t.verbose = true
9
+ end
10
+
11
+ Rake::TestTask.new(:integration) do |t|
12
+ t.libs << "test"
13
+ t.test_files = FileList['test/integration/**/*_test.rb']
14
+ t.verbose = true
15
+ end
16
+ end
17
+
18
+ task :test => ['test:unit', 'test:integration']
19
+ task :default => :test
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "active_record_query/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "active_record_query"
7
+ s.version = ARQuery::VERSION
8
+ s.authors = ["Jon Leighton"]
9
+ s.email = ["j@jonathanleighton.com"]
10
+ s.homepage = "https://github.com/jonleighton/active_record_query"
11
+ s.summary = %q{Proof of concept for proposed new Active Record query API}
12
+ s.description = %q{Proof of concept for proposed new Active Record query API}
13
+
14
+ s.rubyforge_project = "ar_query"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency 'activerecord'
22
+
23
+ s.add_development_dependency 'minitest'
24
+ s.add_development_dependency 'mocha'
25
+ end
@@ -0,0 +1,57 @@
1
+ require 'arel'
2
+
3
+ module ActiveRecord
4
+ class Query < BasicObject
5
+ ::Kernel.require 'active_record/query/subject'
6
+ ::Kernel.require 'active_record/query/context'
7
+
8
+ CONTEXT = {
9
+ :or => Or,
10
+ :and => And
11
+ }
12
+
13
+ attr_reader :table
14
+
15
+ def initialize(table, context)
16
+ @table = table
17
+ @nodes = []
18
+ @stack = [CONTEXT[context].new]
19
+ end
20
+
21
+ def method_missing(name, *args, &block)
22
+ if args.empty? && !block
23
+ Subject.new(self, name)
24
+ else
25
+ ::Kernel.raise ::ArgumentError
26
+ end
27
+ end
28
+
29
+ def respond_to?(name, include_private = nil)
30
+ true
31
+ end
32
+
33
+ def and
34
+ @stack << And.new
35
+ yield
36
+ self << @stack.pop.arel
37
+ end
38
+
39
+ def or
40
+ @stack << Or.new
41
+ yield
42
+ self << @stack.pop.arel
43
+ end
44
+
45
+ def <<(node)
46
+ @stack.last << node
47
+ end
48
+
49
+ def arel
50
+ @stack.last.arel
51
+ end
52
+
53
+ def inspect
54
+ "#<ActiveRecord::Query table=#{table.inspect}>"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,39 @@
1
+ class ActiveRecord::Query
2
+ class Context
3
+ attr_reader :nodes
4
+
5
+ def initialize
6
+ @nodes = []
7
+ end
8
+
9
+ def <<(node)
10
+ @nodes << node
11
+ end
12
+
13
+ def arel
14
+ Arel::Nodes::Grouping.new(nodes)
15
+ end
16
+ end
17
+
18
+ class Or < Context
19
+ def nodes
20
+ @nodes.inject { |mem, node| Arel::Nodes::Or.new(mem, node) }
21
+ end
22
+
23
+ def <<(node)
24
+ super
25
+ false
26
+ end
27
+ end
28
+
29
+ class And < Context
30
+ def nodes
31
+ Arel::Nodes::And.new(@nodes)
32
+ end
33
+
34
+ def <<(node)
35
+ super
36
+ true
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,67 @@
1
+ class ActiveRecord::Query
2
+ class Subject < BasicObject
3
+ def initialize(owner, name, table = nil)
4
+ @owner = owner
5
+ @name = name
6
+ @table = table || @owner.table
7
+ end
8
+
9
+ def method_missing(method_name, *args, &block)
10
+ if args.empty? && !block
11
+ Subject.new(@owner, method_name, ::Arel::Table.new(@name))
12
+ else
13
+ ::Kernel.raise ::ArgumentError
14
+ end
15
+ end
16
+
17
+ def respond_to?(name, include_private = nil)
18
+ true
19
+ end
20
+
21
+ def ==(value)
22
+ __add__ :eq, value
23
+ end
24
+
25
+ def !=(value)
26
+ __add__ :not_eq, value
27
+ end
28
+
29
+ def =~(value)
30
+ __add__ :matches, value
31
+ end
32
+
33
+ def !~(value)
34
+ __add__ :does_not_match, value
35
+ end
36
+
37
+ def >(value)
38
+ __add__ :gt, value
39
+ end
40
+
41
+ def <(value)
42
+ __add__ :lt, value
43
+ end
44
+
45
+ def >=(value)
46
+ __add__ :gteq, value
47
+ end
48
+
49
+ def <=(value)
50
+ __add__ :lteq, value
51
+ end
52
+
53
+ def in(value)
54
+ __add__ :in, value
55
+ end
56
+
57
+ def not_in(value)
58
+ __add__ :not_in, value
59
+ end
60
+
61
+ private
62
+
63
+ def __add__(operator, value)
64
+ @owner << @table[@name].send(operator, value)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,4 @@
1
+ require 'active_record/query'
2
+
3
+ require 'active_record_query/version'
4
+ require 'active_record_query/relation_extension'
@@ -0,0 +1,33 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module ActiveRecordQuery
4
+ module RelationExtension
5
+ def where(*args)
6
+ if args.empty? && block_given?
7
+ query = ActiveRecord::Query.new(table, :and)
8
+ yield query
9
+ super query.arel
10
+ else
11
+ super
12
+ end
13
+ end
14
+
15
+ def any
16
+ query = ActiveRecord::Query.new(table, :or)
17
+ yield query
18
+ where(query.arel)
19
+ end
20
+ end
21
+ end
22
+
23
+ if defined?(ActiveRecord::Base)
24
+ require 'active_record/relation'
25
+
26
+ class ActiveRecord::Relation
27
+ include ActiveRecordQuery::RelationExtension
28
+ end
29
+
30
+ class << ActiveRecord::Base
31
+ delegate :any, :to => :scoped
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module ARQuery
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'active_record_query'
3
+ require 'minitest/autorun'
4
+ require 'mocha'
@@ -0,0 +1,99 @@
1
+ require 'active_record'
2
+ require 'helper'
3
+
4
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
5
+
6
+ ActiveRecord::Migration.verbose = false
7
+ ActiveRecord::Schema.define do
8
+ create_table :posts do |t|
9
+ t.string :title
10
+ end
11
+
12
+ create_table :comments do |t|
13
+ t.references :post
14
+ t.string :author
15
+ end
16
+ end
17
+
18
+ class Post < ActiveRecord::Base
19
+ has_many :comments
20
+ end
21
+
22
+ class Comment < ActiveRecord::Base
23
+ end
24
+
25
+ module ActiveRecord
26
+ class QueryIntegrationTest < MiniTest::Unit::TestCase
27
+ def setup
28
+ @hello = Post.create(title: 'Hello')
29
+ @goodbye = Post.create(title: 'Goodbye')
30
+
31
+ @hello.comments.create(author: 'Jon')
32
+ end
33
+
34
+ def teardown
35
+ Post.delete_all
36
+ end
37
+
38
+ # Checks that none of the operators blow up
39
+ def test_operators
40
+ Post.where do |q|
41
+ q.title == 'x'
42
+ q.title != 'x'
43
+ q.title =~ 'x'
44
+ q.title !~ 'x'
45
+ q.title > 'x'
46
+ q.title < 'x'
47
+ q.title >= 'x'
48
+ q.title <= 'x'
49
+ q.title.in 'x'
50
+ q.title.not_in 'x'
51
+ end
52
+ end
53
+
54
+ def test_basic_query
55
+ assert_equal [@hello], Post.where { |q| q.title == 'Hello' }
56
+ end
57
+
58
+ def test_and_query
59
+ assert_equal [@hello], Post.where { |q| q.title == 'Hello' && q.title =~ 'Hell%' }
60
+ assert_equal [], Post.where { |q| q.title == 'Hello' && q.title == 'Goodbye' }
61
+ end
62
+
63
+ def test_or_query
64
+ assert_equal [@hello, @goodbye], Post.any { |q| q.title == 'Hello' || q.title == 'Goodbye' }
65
+ assert_equal [@hello], Post.any { |q| q.title == 'Hello' || q.title == 'non-existent' }
66
+ assert_equal [@hello], Post.any { |q| q.title == 'non-existent' || q.title == 'Hello' }
67
+ end
68
+
69
+ def test_other_table_query
70
+ assert_equal [@hello], Post.joins(:comments).where { |q| q.comments.author == 'Jon' }
71
+ assert_equal [], Post.joins(:comments).where { |q| q.comments.author == 'Bob' }
72
+ end
73
+
74
+ def test_nested_contexts
75
+ query = Post.any { |q|
76
+ q.title == 'non-existent' ||
77
+ q.and {
78
+ q.title == 'Hello' &&
79
+ q.or {
80
+ q.title == 'other' ||
81
+ q.title =~ 'Hell%'
82
+ }
83
+ }
84
+ }
85
+
86
+ assert query.to_sql.include?(
87
+ %q{("posts"."title" = 'non-existent' OR ("posts"."title" = 'Hello' AND ("posts"."title" = 'other' OR "posts"."title" LIKE 'Hell%')))}
88
+ )
89
+
90
+ assert_equal [@hello], query
91
+
92
+ query = Post.any { |q| q.and { q.title == 'a' && q.title == 'b' } || q.title == 'c' }
93
+
94
+ assert query.to_sql.include?(
95
+ %q{(("posts"."title" = 'a' AND "posts"."title" = 'b') OR "posts"."title" = 'c')}
96
+ )
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,108 @@
1
+ require 'helper'
2
+
3
+ class SubjectTest < MiniTest::Unit::TestCase
4
+ class Column
5
+ attr_reader :table, :name
6
+
7
+ def initialize(table, name)
8
+ @table = table
9
+ @name = name
10
+ end
11
+
12
+ def ==(other)
13
+ other.class == self.class &&
14
+ other.table == table &&
15
+ other.name == name
16
+ end
17
+
18
+ PREDICATES = [:eq, :not_eq, :matches, :does_not_match, :gt, :lt, :gteq, :lteq, :in, :not_in]
19
+
20
+ PREDICATES.each do |pred|
21
+ define_method pred do |other|
22
+ [pred, self, other]
23
+ end
24
+ end
25
+ end
26
+
27
+ class Table
28
+ def [](name)
29
+ Column.new(self, name)
30
+ end
31
+ end
32
+
33
+ def setup
34
+ @owner = stub
35
+ @table = Table.new
36
+ @subject = ActiveRecord::Query::Subject.new(@owner, :foo, @table)
37
+ @column = @table[:foo]
38
+ end
39
+
40
+ def test_equality
41
+ @owner.expects(:<<).with(@column.eq(:bar))
42
+ @subject == :bar
43
+ end
44
+
45
+ def test_inequality
46
+ @owner.expects(:<<).with(@column.not_eq(:bar))
47
+ @subject != :bar
48
+ end
49
+
50
+ def test_matches
51
+ @owner.expects(:<<).with(@column.matches(:bar))
52
+ @subject =~ :bar
53
+ end
54
+
55
+ def test_does_not_match
56
+ @owner.expects(:<<).with(@column.does_not_match(:bar))
57
+ @subject !~ :bar
58
+ end
59
+
60
+ def test_gt
61
+ @owner.expects(:<<).with(@column.gt(:bar))
62
+ @subject > :bar
63
+ end
64
+
65
+ def test_lt
66
+ @owner.expects(:<<).with(@column.lt(:bar))
67
+ @subject < :bar
68
+ end
69
+
70
+ def test_gteq
71
+ @owner.expects(:<<).with(@column.gteq(:bar))
72
+ @subject >= :bar
73
+ end
74
+
75
+ def test_lteq
76
+ @owner.expects(:<<).with(@column.lteq(:bar))
77
+ @subject <= :bar
78
+ end
79
+
80
+ def test_in
81
+ @owner.expects(:<<).with(@column.in(:bar))
82
+ @subject.in(:bar)
83
+ end
84
+
85
+ def test_not_in
86
+ @owner.expects(:<<).with(@column.not_in(:bar))
87
+ @subject.not_in(:bar)
88
+ end
89
+
90
+ def test_method_missing
91
+ table = stub
92
+ sub2 = stub
93
+
94
+ Arel::Table.expects(:new).with(:foo).returns(table)
95
+ ActiveRecord::Query::Subject.expects(:new).with(@owner, :bar, table).returns(sub2)
96
+
97
+ assert_equal sub2, @subject.bar
98
+ end
99
+
100
+ def test_method_missing_with_args
101
+ assert_raises(ArgumentError) { @subject.bar(:foo) }
102
+ assert_raises(ArgumentError) { @subject.bar { :foo } }
103
+ end
104
+
105
+ def test_respond_to
106
+ assert @subject.respond_to?(:bar)
107
+ end
108
+ end
@@ -0,0 +1,57 @@
1
+ require 'helper'
2
+
3
+ module ActiveRecord
4
+ class QueryTest < MiniTest::Unit::TestCase
5
+ def setup
6
+ @table = :table
7
+ @query = Query.new(@table, :and)
8
+ class << @query; include ::Mocha::ObjectMethods; end
9
+ end
10
+
11
+ def test_method_missing
12
+ assert_raises(ArgumentError) { @query.title(:foo) }
13
+ assert_raises(ArgumentError) { @query.title { :foo } }
14
+
15
+ subject = stub
16
+ Query::Subject.expects(:new).with(@query, :title).returns(subject)
17
+ assert_equal subject, @query.title
18
+ end
19
+
20
+ def test_respond_to
21
+ assert @query.respond_to?(:title)
22
+ end
23
+
24
+ def test_table
25
+ assert_equal @table, @query.table
26
+ end
27
+
28
+ def test_push
29
+ @query << :foo
30
+ @query << :bar
31
+
32
+ assert_equal [:foo, :bar], @query.arel.expr.children
33
+ end
34
+
35
+ def test_inspect
36
+ assert_equal "#<ActiveRecord::Query table=:table>", @query.inspect
37
+ end
38
+
39
+ def test_and
40
+ subcontext = stub(:arel => stub)
41
+ subject = stub
42
+ Query::And.expects(:new).returns(subcontext)
43
+
44
+ @query.and { }
45
+ assert_equal [subcontext.arel], @query.arel.expr.children
46
+ end
47
+
48
+ def test_or
49
+ subcontext = stub(:arel => stub)
50
+ subject = stub
51
+ Query::Or.expects(:new).returns(subcontext)
52
+
53
+ @query.or { }
54
+ assert_equal [subcontext.arel], @query.arel.expr.children
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ require 'helper'
2
+
3
+ module ActiveRecordQuery
4
+ class RelationExtensionTest < MiniTest::Unit::TestCase
5
+ class FakeRelation
6
+ module Where
7
+ attr_reader :where_values
8
+
9
+ def where(*args)
10
+ @where_values = args
11
+ end
12
+ end
13
+
14
+ include Where
15
+ include RelationExtension
16
+
17
+ def table
18
+ :table
19
+ end
20
+ end
21
+
22
+ def setup
23
+ @relation = FakeRelation.new
24
+ @query = stub(:arel => stub)
25
+ end
26
+
27
+ def test_where_with_args
28
+ @relation.where :foo
29
+ assert_equal [:foo], @relation.where_values
30
+
31
+ @relation.where(:foo) { :bar }
32
+ assert_equal [:foo], @relation.where_values
33
+ end
34
+
35
+ def test_where_with_block
36
+ ActiveRecord::Query.expects(:new).with(:table, :and).returns(@query)
37
+
38
+ query = nil
39
+ @relation.where { |q| query = q }
40
+
41
+ assert_equal @query, query
42
+ assert_equal [@query.arel], @relation.where_values
43
+ end
44
+
45
+ def test_any
46
+ ActiveRecord::Query.expects(:new).with(:table, :or).returns(@query)
47
+
48
+ query = nil
49
+ @relation.any { |q| query = q }
50
+
51
+ assert_equal @query, query
52
+ assert_equal [@query.arel], @relation.where_values
53
+ end
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_record_query
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jon Leighton
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: &15665600 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *15665600
25
+ - !ruby/object:Gem::Dependency
26
+ name: minitest
27
+ requirement: &15698260 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *15698260
36
+ - !ruby/object:Gem::Dependency
37
+ name: mocha
38
+ requirement: &15719140 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *15719140
47
+ description: Proof of concept for proposed new Active Record query API
48
+ email:
49
+ - j@jonathanleighton.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - README.md
57
+ - Rakefile
58
+ - active_record_query.gemspec
59
+ - lib/active_record/query.rb
60
+ - lib/active_record/query/context.rb
61
+ - lib/active_record/query/subject.rb
62
+ - lib/active_record_query.rb
63
+ - lib/active_record_query/relation_extension.rb
64
+ - lib/active_record_query/version.rb
65
+ - test/helper.rb
66
+ - test/integration/query_test.rb
67
+ - test/unit/query/subject_test.rb
68
+ - test/unit/query_test.rb
69
+ - test/unit/relation_extension_test.rb
70
+ homepage: https://github.com/jonleighton/active_record_query
71
+ licenses: []
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project: ar_query
90
+ rubygems_version: 1.8.15
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Proof of concept for proposed new Active Record query API
94
+ test_files:
95
+ - test/helper.rb
96
+ - test/integration/query_test.rb
97
+ - test/unit/query/subject_test.rb
98
+ - test/unit/query_test.rb
99
+ - test/unit/relation_extension_test.rb
100
+ has_rdoc: