baby_squeel 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4dec1d36d240f113d9122c83549c06601f29124c
4
+ data.tar.gz: f97d4bc708832c1895e0e612c6bd76dfe63b6877
5
+ SHA512:
6
+ metadata.gz: 7be1b2e3d861f50328cb1a9b05f8e02099a1db3c33dfeda3259a77667291b8f3b373d3e0a395230b1b64af7b8d8c2d559f52e31752c7e20401371e5eaa1fecaf
7
+ data.tar.gz: 877ad6134d70a905a9142f92265e059ef48e49edd9ef513445a966b0a2a22758c0e1792be9180b749be5d2673e5e817a15550fe93e465eb61e2440709bd9b770
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,15 @@
1
+ Lint/AssignmentInCondition:
2
+ Enabled: false
3
+
4
+ Style/BlockDelimiters:
5
+ Enabled: false
6
+
7
+ Style/Documentation:
8
+ Enabled: false
9
+
10
+ Style/NilComparison:
11
+ Exclude:
12
+ - spec/**/*
13
+
14
+ Metrics/LineLength:
15
+ Max: 100
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 2.2.4
5
+
6
+ before_install: gem install bundler -v 1.11.2
7
+ before_script: bundle exec rubocop
8
+ after_script: bundle exec rake coveralls:push
9
+
10
+ env:
11
+ global:
12
+ - COVERALLS_REPO_TOKEN=X5ZWSPW8VW2JXNJO0qNjlZCQNW4ju89gb
13
+ matrix:
14
+ - AR=4.0.0
15
+ - AR=4.1.0
16
+ - AR=4.1.15
17
+ - AR=4.2.0
18
+ - AR=4.2.6
19
+ - AR=5.0.0.beta3
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in baby_squeel.gemspec
4
+ gemspec
5
+
6
+ if ar_version = ENV['AR']
7
+ gem 'activerecord', ar_version
8
+ else
9
+ gem 'activerecord', github: 'rails/rails'
10
+ end
11
+
12
+ group :test do
13
+ gem 'pry'
14
+ gem 'coveralls'
15
+ gem 'simplecov'
16
+ end
17
+
18
+ gem 'rubocop', require: false
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Ray Zane
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,170 @@
1
+ # BabySqueel
2
+
3
+ [![Build Status](https://travis-ci.org/rzane/baby_squeel.svg?branch=master)](https://travis-ci.org/rzane/baby_squeel)
4
+ [![Code Climate](https://codeclimate.com/github/rzane/baby_squeel/badges/gpa.svg)](https://codeclimate.com/github/rzane/baby_squeel)
5
+ [![Coverage Status](https://coveralls.io/repos/github/rzane/baby_squeel/badge.svg?branch=master)](https://coveralls.io/github/rzane/baby_squeel?branch=master)
6
+
7
+ ![biddy piggy](http://static.thefrisky.com/uploads/2010/07/01/pig_in_boots_070110_m.jpg)
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.
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 also suprisingly uncomplicated. It's really just a layer of sugar on top of Arel.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'baby_squeel'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install baby_squeel
30
+
31
+ ## Usage
32
+
33
+ Okay, so we have a `Post` model:
34
+
35
+ ```ruby
36
+ class Post < ActiveRecord::Base
37
+ belongs_to :author
38
+ end
39
+ ```
40
+
41
+ #### Selects
42
+
43
+ ```ruby
44
+ Post.selecting { (id + 5).as('id_plus_five') }
45
+ # SELECT "posts"."id" + 5 AS id_plus_five FROM "posts"
46
+
47
+ Post.selecting { id.sum }
48
+ # SELECT SUM("posts"."id") FROM "posts"
49
+
50
+ Post.joins(:author).selecting { [id, author.id] }
51
+ # SELECT "posts"."id", "author"."id"
52
+ # FROM "posts"
53
+ # INNER JOIN "authors" ON "posts"."author_id" = "authors"."id"
54
+ ```
55
+
56
+ #### Joins
57
+
58
+ ```ruby
59
+ Post.joining { author }
60
+ # SELECT "posts".* FROM "posts"
61
+ # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
62
+
63
+ Post.joining { [author.outer, comments] }
64
+ # SELECT "posts".* FROM "posts"
65
+ # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
66
+ # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
67
+
68
+ Post.joining { author.comments }
69
+ # SELECT "posts".* FROM "posts"
70
+ # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
71
+ # INNER JOIN "comments" ON "comments"."author_id" = "authors"."id"
72
+
73
+ Post.joining { author.outer.comments.outer }
74
+ # SELECT "posts".* FROM "posts"
75
+ # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
76
+ # LEFT OUTER JOIN "comments" ON "comments"."author_id" = "authors"."id"
77
+
78
+ Post.joining { author.comments.outer }
79
+ # SELECT "posts".* FROM "posts"
80
+ # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
81
+ # LEFT OUTER JOIN "comments" ON "comments"."author_id" = "authors"."id"
82
+
83
+ Post.joining { author.alias('a').on((author.id == author_id) | (author.name == title)) }
84
+ # SELECT "posts".* FROM "posts"
85
+ # INNER JOIN "authors" "a" ON (
86
+ # "authors"."id" = "posts"."author_id" OR
87
+ # "authors"."name" = "posts"."title"
88
+ # )
89
+ ```
90
+
91
+ #### Wheres
92
+
93
+ ```ruby
94
+ Post.where.has { title == 'My Post' }
95
+ # SELECT "posts".* FROM "posts" WHERE "posts"."title" = 'My Post'
96
+
97
+ Post.where.has { title =~ 'My P%' }
98
+ # SELECT "posts".* FROM "posts" WHERE "posts"."title" LIKE 'My P%'
99
+
100
+ Author.where.has { (name =~ 'Ray%') & (id < 5) | (name.lower =~ 'zane%') & (id > 100) }
101
+ # SELECT "authors".* FROM "authors"
102
+ # WHERE (
103
+ # "authors"."name" LIKE 'Ray%' AND "authors"."id" < 5 OR
104
+ # LOWER("authors"."name") LIKE 'zane%' AND "authors"."id" > 100
105
+ # )
106
+
107
+ Post.joins(:author).where.has { author.name == 'Ray' }
108
+ # SELECT "posts".* FROM "posts"
109
+ # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
110
+ # WHERE "authors"."name" = 'Ray'
111
+ ```
112
+
113
+ #### Orders
114
+
115
+ ```ruby
116
+ Post.ordering { [id.desc, title.asc] }
117
+ # SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC, "posts"."title" ASC
118
+
119
+ Post.ordering { (id * 5).desc }
120
+ # SELECT "posts".* FROM "posts" ORDER BY "posts"."id" * 5 DESC
121
+
122
+ Post.select(:author_id).group(:author_id).ordering { id.count.desc }
123
+ # SELECT "posts"."author_id"
124
+ # FROM "posts" GROUP BY "posts"."author_id"
125
+ # ORDER BY COUNT("posts"."id") DESC
126
+
127
+ Post.joins(:author).ordering { author.id.desc }
128
+ # SELECT "posts".* FROM "posts"
129
+ # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
130
+ # ORDER BY "authors"."id" DESC
131
+ ```
132
+
133
+ #### Functions
134
+
135
+ ```ruby
136
+ Post.selecting { coalesce(author_id, 5).as('author_id_with_default') }
137
+ # SELECT coalesce("posts"."author_id", 5) AS author_id_with_default FROM "posts"
138
+ ```
139
+
140
+ ## Important Notes
141
+
142
+ While inside one of BabySqueel's blocks, `self` will be something totally different. You won't have access to your instance variables or methods.
143
+
144
+ Don't worry, there's an easy solution. Just give arity to the block:
145
+
146
+ ```ruby
147
+ Post.where.has { |table| table.title == 'Test' }
148
+ # SELECT "posts".* WHERE "posts"."title" = 'Test'
149
+ ```
150
+
151
+ ## Development
152
+
153
+ 1. Pick an ActiveRecord version to develop against, then export it: `export AR=4.2.6`.
154
+ 2. Run `bin/setup` to install dependencies.
155
+ 3. Run `rake` to run the specs.
156
+
157
+ You can also run `bin/console` to open up a prompt where you'll have access to some models to experiment with.
158
+
159
+ ## Todo
160
+
161
+ I'd like to support complex joins with explicit outer joins and aliasing.
162
+
163
+ ## Contributing
164
+
165
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rzane/baby_squeel.
166
+
167
+
168
+ ## License
169
+
170
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,32 @@
1
+ require 'yaml'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+ require 'coveralls/rake/task'
5
+
6
+ Coveralls::RakeTask.new
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ def run_version(version, cmd)
11
+ display = "AR=#{version} #{cmd}"
12
+ puts display
13
+
14
+ Bundler.with_clean_env do
15
+ system({ 'AR' => version }, cmd)
16
+ end
17
+
18
+ abort "\nFAILED: #{display}" unless $CHILD_STATUS.success?
19
+ end
20
+
21
+ task 'spec:matrix' do
22
+ travis = YAML.load_file '.travis.yml'
23
+
24
+ travis['env']['matrix'].reverse.each do |variable|
25
+ version = variable[/\=(.+)$/, 1]
26
+ rm_rf 'Gemfile.lock'
27
+ run_version version, 'bundle install > /dev/null'
28
+ run_version version, 'rake spec'
29
+ end
30
+ end
31
+
32
+ task default: :spec
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'baby_squeel/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'baby_squeel'
8
+ spec.version = BabySqueel::VERSION
9
+ spec.authors = ['Ray Zane']
10
+ spec.email = ['ray@promptworks.com']
11
+
12
+ spec.summary = 'A tiny squeel implementation without all of the evil.'
13
+ spec.description = spec.summary
14
+ spec.homepage = 'https://github.com/rzane/baby_squeel'
15
+ spec.license = 'MIT'
16
+
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
22
+ f.match(%r{^(test|spec|features)/})
23
+ end
24
+
25
+ spec.add_dependency 'activerecord', '>= 4.0.0'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.11'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ spec.add_development_dependency 'rspec', '~> 3.4.0'
30
+ spec.add_development_dependency 'sqlite3'
31
+ end
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'baby_squeel'
5
+ require_relative '../spec/support/schema'
6
+ require_relative '../spec/support/models'
7
+
8
+ # You can add fixtures and/or initialization code here to make experimenting
9
+ # with your gem easier. You can also use a different console, if you like.
10
+
11
+ puts 'Creating 2 authors...'
12
+ authors = [
13
+ Author.create!(name: 'Ray Zane'),
14
+ Author.create!(name: 'Jimmy Hoffa')
15
+ ]
16
+
17
+ puts 'Creating 15 posts...'
18
+ 15.times do |n|
19
+ Post.create! title: "Post #{n}", author: authors.sample
20
+ end
21
+
22
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
23
+
24
+ require 'pry'
25
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,10 @@
1
+ require 'active_record'
2
+ require 'active_record/relation'
3
+ require 'baby_squeel/version'
4
+ require 'baby_squeel/active_record'
5
+
6
+ module BabySqueel
7
+ end
8
+
9
+ ::ActiveRecord::Base.extend BabySqueel::ActiveRecord::QueryMethods
10
+ ::ActiveRecord::QueryMethods::WhereChain.prepend BabySqueel::ActiveRecord::WhereChain
@@ -0,0 +1,35 @@
1
+ require 'baby_squeel/dsl'
2
+
3
+ module BabySqueel
4
+ module ActiveRecord
5
+ module QueryMethods
6
+ def joining(&block)
7
+ joins DSL.evaluate(self, &block)
8
+ end
9
+
10
+ def selecting(&block)
11
+ select DSL.evaluate(self, &block)
12
+ end
13
+
14
+ def ordering(&block)
15
+ order DSL.evaluate(self, &block)
16
+ end
17
+ end
18
+
19
+ module WhereChain
20
+ if ::ActiveRecord::VERSION::MAJOR > 4
21
+ def has(&block)
22
+ opts = DSL.evaluate(@scope, &block)
23
+ factory = @scope.send(:where_clause_factory)
24
+ @scope.where_clause += factory.build(opts, [])
25
+ @scope
26
+ end
27
+ else
28
+ def has(&block)
29
+ @scope.where_values += [DSL.evaluate(@scope, &block)]
30
+ @scope
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ require 'baby_squeel/table'
2
+ require 'baby_squeel/join_dependency'
3
+
4
+ module BabySqueel
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
+
16
+ class Association < Table
17
+ def initialize(parent, reflection)
18
+ @parent = parent
19
+ @reflection = reflection
20
+ super(@reflection.klass)
21
+ end
22
+
23
+ def _arel
24
+ props[:on] ? super : ([*@parent._arel] + join_constraints)
25
+ end
26
+
27
+ private
28
+
29
+ def join_constraints
30
+ if props[:table].is_a? Arel::Nodes::TableAlias
31
+ raise AliasingError.new(@reflection.name, props[:table].right)
32
+ end
33
+
34
+ JoinDependency.new(@scope, @reflection, props[:join]).constraints
35
+ end
36
+
37
+ def spawn
38
+ Association.new(@parent, @reflection).tap do |table|
39
+ table.props = props
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ require 'baby_squeel/nodes'
2
+ require 'baby_squeel/table'
3
+ require 'baby_squeel/association'
4
+
5
+ module BabySqueel
6
+ class DSL < Table
7
+ def self.evaluate(scope, &block)
8
+ new(scope).evaluate(&block)
9
+ end
10
+
11
+ def func(name, *args)
12
+ Nodes.wrap Arel::Nodes::NamedFunction.new(name.to_s, args)
13
+ end
14
+
15
+ def evaluate(&block)
16
+ if block.arity.zero?
17
+ Nodes.unwrap instance_eval(&block)
18
+ else
19
+ Nodes.unwrap yield(self)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def method_missing(meth, *args, &block)
26
+ if !args.empty? && !block_given?
27
+ func(meth, args)
28
+ else
29
+ super
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,55 @@
1
+ # Props to Arel Helpers
2
+ # https://github.com/camertron/arel-helpers/blob/master/lib/arel-helpers/join_association.rb
3
+ #
4
+ # This is really, really ugly.
5
+ #
6
+ module BabySqueel
7
+ class JoinDependency
8
+ def initialize(scope, reflection, join_type)
9
+ @scope = scope
10
+ @reflection = reflection
11
+ @join_type = join_type
12
+ end
13
+
14
+ if ::ActiveRecord::VERSION::MAJOR > 4
15
+ def constraints
16
+ dependency.join_constraints([], @join_type).flat_map(&:joins)
17
+ end
18
+ elsif ::ActiveRecord::VERSION::STRING >= '4.2.0'
19
+ def constraints
20
+ dependency.join_constraints([]).flat_map do |constraint|
21
+ constraint.joins.map { |join| rebuild join }
22
+ end
23
+ end
24
+ elsif ::ActiveRecord::VERSION::STRING >= '4.1.0'
25
+ def constraints
26
+ dependency.join_constraints([]).flat_map { |join| rebuild join }
27
+ end
28
+ else
29
+ def constraints
30
+ manager = Arel::SelectManager.new(@scope)
31
+
32
+ dependency.join_associations.each do |assoc|
33
+ assoc.join_type = @join_type
34
+ assoc.join_to(manager)
35
+ end
36
+
37
+ manager.join_sources
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def rebuild(join)
44
+ @join_type.new(join.left, join.right)
45
+ end
46
+
47
+ def dependency
48
+ ::ActiveRecord::Associations::JoinDependency.new(
49
+ @reflection.active_record,
50
+ [@reflection.name],
51
+ []
52
+ )
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,68 @@
1
+ require 'baby_squeel/operators'
2
+
3
+ module BabySqueel
4
+ module Nodes
5
+ class << self
6
+ def wrap(arel)
7
+ if arel.class.parents.include?(Arel)
8
+ Generic.new(arel)
9
+ else
10
+ arel
11
+ end
12
+ end
13
+
14
+ def unwrap(node)
15
+ if node.respond_to? :_arel
16
+ node._arel
17
+ elsif node.is_a? Array
18
+ node.map { |n| unwrap(n) }
19
+ else
20
+ node
21
+ end
22
+ end
23
+ end
24
+
25
+ # This proxy class allows us to quack like
26
+ # any arel object. When a method missing is
27
+ # hit, we'll instantiate a new proxy object.
28
+ class Proxy < ActiveSupport::ProxyObject
29
+ # Resolve constants the normal way
30
+ def self.const_missing(name)
31
+ ::Object.const_get(name)
32
+ end
33
+
34
+ attr_reader :_arel
35
+
36
+ def initialize(arel)
37
+ @_arel = Nodes.unwrap(arel)
38
+ end
39
+
40
+ def respond_to?(meth, include_private = false)
41
+ meth.to_s == '_arel' || _arel.respond_to?(meth, include_private)
42
+ end
43
+
44
+ private
45
+
46
+ def method_missing(meth, *args, &block)
47
+ if _arel.respond_to?(meth)
48
+ Nodes.wrap _arel.send(meth, *Nodes.unwrap(args), &block)
49
+ else
50
+ super
51
+ end
52
+ end
53
+ end
54
+
55
+ # This is a generic proxy class that includes
56
+ # all possible modules. In the future, these
57
+ # proxy classes should be more specific and
58
+ # only include necessary/applicable modules.
59
+ class Generic < Proxy
60
+ include Arel::OrderPredications
61
+ include Operators::Comparison
62
+ include Operators::Equality
63
+ include Operators::Generic
64
+ include Operators::Grouping
65
+ include Operators::Matching
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,47 @@
1
+ module BabySqueel
2
+ module Operators
3
+ module ArelAliasing
4
+ def arel_alias(operator, arel_name)
5
+ define_method operator do |other|
6
+ send(arel_name, other)
7
+ end
8
+ end
9
+ end
10
+
11
+ module Comparison
12
+ extend ArelAliasing
13
+ arel_alias :<, :lt
14
+ arel_alias :>, :gt
15
+ arel_alias :<=, :lteq
16
+ arel_alias :>=, :gteq
17
+ end
18
+
19
+ module Equality
20
+ extend ArelAliasing
21
+ arel_alias :==, :eq
22
+ arel_alias :'!=', :not_eq
23
+ end
24
+
25
+ module Generic
26
+ def op(operator, other)
27
+ Nodes.wrap Arel::Nodes::InfixOperation.new(operator, self, other)
28
+ end
29
+ end
30
+
31
+ module Grouping
32
+ extend ArelAliasing
33
+ arel_alias :&, :and
34
+ arel_alias :|, :or
35
+ end
36
+
37
+ module Matching
38
+ extend ArelAliasing
39
+ arel_alias :=~, :matches
40
+ arel_alias :'!~', :does_not_match
41
+ arel_alias :like, :matches
42
+ arel_alias :not_like, :does_not_match
43
+ arel_alias :like_any, :matches_any
44
+ arel_alias :not_like_any, :does_not_match_any
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,99 @@
1
+ module BabySqueel
2
+ class AssociationNotFoundError < StandardError
3
+ def initialize(scope, name)
4
+ super "Association named '#{name}' was not found on #{scope.model_name}"
5
+ end
6
+ end
7
+
8
+ class Table
9
+ attr_writer :props
10
+
11
+ def initialize(scope)
12
+ @scope = scope
13
+ end
14
+
15
+ def [](key)
16
+ Nodes.wrap props[:table][key]
17
+ end
18
+
19
+ def association(name)
20
+ if reflection = @scope.reflect_on_association(name)
21
+ Association.new(self, reflection)
22
+ else
23
+ raise AssociationNotFoundError.new(@scope, name)
24
+ end
25
+ end
26
+
27
+ def alias(alias_name)
28
+ spawn.alias! alias_name
29
+ end
30
+
31
+ def alias!(alias_name)
32
+ props.store :table, props[:table].alias(alias_name)
33
+ self
34
+ end
35
+
36
+ def outer
37
+ spawn.outer!
38
+ end
39
+
40
+ def outer!
41
+ props.store :join, Arel::Nodes::OuterJoin
42
+ self
43
+ end
44
+
45
+ def inner
46
+ spawn.inner!
47
+ end
48
+
49
+ def inner!
50
+ props.store :join, Arel::Nodes::InnerJoin
51
+ self
52
+ end
53
+
54
+ def on(node)
55
+ spawn.on! node
56
+ end
57
+
58
+ def on!(node)
59
+ props.store :on, Arel::Nodes::On.new(node)
60
+ self
61
+ end
62
+
63
+ def _arel
64
+ props[:join].new(props[:table], props[:on]) if props[:on]
65
+ end
66
+
67
+ private
68
+
69
+ def props
70
+ @props ||= { table: @scope.arel_table, join: Arel::Nodes::InnerJoin }
71
+ end
72
+
73
+ def spawn
74
+ Table.new(@scope).tap do |table|
75
+ table.props = props
76
+ end
77
+ end
78
+
79
+ def resolve(name)
80
+ if @scope.column_names.include?(name.to_s)
81
+ self[name]
82
+ elsif @scope.reflect_on_association(name)
83
+ association(name)
84
+ end
85
+ end
86
+
87
+ def respond_to_missing?(name, *)
88
+ resolve(name).present? || super
89
+ end
90
+
91
+ def method_missing(name, *args, &block)
92
+ if !args.empty? || block_given?
93
+ super
94
+ else
95
+ resolve(name) || super
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ module BabySqueel
2
+ VERSION = '0.1.0'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: baby_squeel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ray Zane
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.4.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.4.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: A tiny squeel implementation without all of the evil.
84
+ email:
85
+ - ray@promptworks.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".rubocop.yml"
93
+ - ".travis.yml"
94
+ - Gemfile
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - baby_squeel.gemspec
99
+ - bin/console
100
+ - bin/setup
101
+ - lib/baby_squeel.rb
102
+ - lib/baby_squeel/active_record.rb
103
+ - lib/baby_squeel/association.rb
104
+ - lib/baby_squeel/dsl.rb
105
+ - lib/baby_squeel/join_dependency.rb
106
+ - lib/baby_squeel/nodes.rb
107
+ - lib/baby_squeel/operators.rb
108
+ - lib/baby_squeel/table.rb
109
+ - lib/baby_squeel/version.rb
110
+ homepage: https://github.com/rzane/baby_squeel
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.4.8
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: A tiny squeel implementation without all of the evil.
134
+ test_files: []