directiverecord 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.
- checksums.yaml +15 -0
 - data/.gitignore +9 -0
 - data/CHANGELOG.rdoc +5 -0
 - data/Gemfile +3 -0
 - data/MIT-LICENSE +20 -0
 - data/README.md +49 -0
 - data/Rakefile +10 -0
 - data/VERSION +1 -0
 - data/directiverecord.gemspec +27 -0
 - data/lib/directive_record.rb +6 -0
 - data/lib/directive_record/gem_ext.rb +1 -0
 - data/lib/directive_record/gem_ext/active_record.rb +2 -0
 - data/lib/directive_record/gem_ext/active_record/base.rb +13 -0
 - data/lib/directive_record/gem_ext/active_record/relation.rb +17 -0
 - data/lib/directive_record/query.rb +25 -0
 - data/lib/directive_record/query/monetdb.rb +54 -0
 - data/lib/directive_record/query/mysql.rb +36 -0
 - data/lib/directive_record/query/sql.rb +291 -0
 - data/lib/directive_record/relation.rb +70 -0
 - data/lib/directive_record/version.rb +7 -0
 - data/lib/directiverecord.rb +1 -0
 - data/test/application/app/models/customer.rb +6 -0
 - data/test/application/app/models/employee.rb +5 -0
 - data/test/application/app/models/office.rb +14 -0
 - data/test/application/app/models/order.rb +4 -0
 - data/test/application/app/models/order_detail.rb +4 -0
 - data/test/application/app/models/payment.rb +3 -0
 - data/test/application/app/models/product.rb +3 -0
 - data/test/application/app/models/product_line.rb +3 -0
 - data/test/application/app/models/tag.rb +3 -0
 - data/test/application/boot.rb +27 -0
 - data/test/application/config/database.yml +8 -0
 - data/test/application/db/database.sql +327 -0
 - data/test/test_helper.rb +17 -0
 - data/test/test_helper/coverage.rb +13 -0
 - data/test/unit/gem_ext/active_record/test_base.rb +30 -0
 - data/test/unit/gem_ext/active_record/test_relation.rb +42 -0
 - data/test/unit/query/test_monetdb.rb +27 -0
 - data/test/unit/query/test_mysql.rb +238 -0
 - data/test/unit/query/test_sql.rb +46 -0
 - data/test/unit/test_directive_record.rb +15 -0
 - data/test/unit/test_query.rb +40 -0
 - data/test/unit/test_relation.rb +42 -0
 - metadata +221 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            !binary "U0hBMQ==":
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: !binary |-
         
     | 
| 
      
 4 
     | 
    
         
            +
                YjYwOWJkMDk4YmUyZWZkMjcwMThiZDk4NzQ1ZTUzMzJiM2YwYzg1NQ==
         
     | 
| 
      
 5 
     | 
    
         
            +
              data.tar.gz: !binary |-
         
     | 
| 
      
 6 
     | 
    
         
            +
                YjA3MDE0YmRjYjA4NTc3NjMyYmZkOGEzMTc4NGUzMjk2NGMwYzgwOQ==
         
     | 
| 
      
 7 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 8 
     | 
    
         
            +
              metadata.gz: !binary |-
         
     | 
| 
      
 9 
     | 
    
         
            +
                YmUxZGI1YmYxNWIzMjMyOGM5MWY2NmFmYWMxZGRlOTEyZjFmYjI3ZWE1Mjdk
         
     | 
| 
      
 10 
     | 
    
         
            +
                M2MwNGIxYzIzMmYwZWU5ZTNiMGJjZmQ5MmRhNjNjNjAyNzdiNmVmZjk2NjYy
         
     | 
| 
      
 11 
     | 
    
         
            +
                NmU4YTc1ODg4OWFkMmRkZmVhMzQ3MjAxMTc1ZmFiNjZmNTI4YzA=
         
     | 
| 
      
 12 
     | 
    
         
            +
              data.tar.gz: !binary |-
         
     | 
| 
      
 13 
     | 
    
         
            +
                YzdiM2Q0ZDI2ODFmMGJiNWNiY2M3MWU1ODQzMjUwMWViZDMxNWU2NzRkYmEy
         
     | 
| 
      
 14 
     | 
    
         
            +
                MTBhZTMyN2YyMTVhZWZjOTU4NGYxMjBiYmRjZDg1YTExYTYyNDRkNTVhNTRh
         
     | 
| 
      
 15 
     | 
    
         
            +
                YjJjZDhjNDFmZDAyMjgxOWZmYzdjY2Y1NDMyN2RhZDQ4NTA4MjM=
         
     | 
    
        data/.gitignore
    ADDED
    
    
    
        data/CHANGELOG.rdoc
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/MIT-LICENSE
    ADDED
    
    | 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2014 Paul Engel
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 4 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 5 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 6 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 7 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 8 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 9 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 12 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 15 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 16 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 17 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 18 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 19 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 20 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ## DirectiveRecord
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            A layer on top of ActiveRecord for using paths within queries without thinking about association joins
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            ### Installation
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Add `DirectiveRecord` in your `Gemfile`:
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                gem "directiverecord"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            Run the following in your console to install with Bundler:
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                $ bundle install
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            ### Demo
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            To try `DirectiveRecord` right out-of-the-box, please clone
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            https://github.com/archan937/directiverecord-console
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            and follow the README instructions. It is provided with a sample database and a Pry console in which you can play with `DirectiveRecord`.
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            The README is also provided with several [straightforward examples](https://github.com/archan937/directiverecord-console#using-the-console).
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            ### Testing
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            Run the following command for testing:
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                $ rake
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            You can also run a single test file:
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                $ ruby test/unit/query/test_mysql.rb
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            ### TODO
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            * Add more tests
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            ### License
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            Copyright (c) 2014 Paul Engel, released under the MIT License
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            http://github.com/archan937 – http://twitter.com/archan937 – http://gettopup.com – pm_engel@icloud.com
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/Rakefile
    ADDED
    
    
    
        data/VERSION
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            0.1.0
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # -*- encoding: utf-8 -*-
         
     | 
| 
      
 2 
     | 
    
         
            +
            require File.expand_path("../lib/directive_record/version", __FILE__)
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            Gem::Specification.new do |gem|
         
     | 
| 
      
 5 
     | 
    
         
            +
              gem.author        = "Paul Engel"
         
     | 
| 
      
 6 
     | 
    
         
            +
              gem.email         = "pm_engel@icloud.com"
         
     | 
| 
      
 7 
     | 
    
         
            +
              gem.summary       = %q{A layer on top of ActiveRecord for using paths within queries without thinking about association joins}
         
     | 
| 
      
 8 
     | 
    
         
            +
              gem.description   = %q{A layer on top of ActiveRecord for using paths within queries without thinking about association joins}
         
     | 
| 
      
 9 
     | 
    
         
            +
              gem.homepage      = "https://github.com/archan937/directiverecord"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              gem.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
         
     | 
| 
      
 12 
     | 
    
         
            +
              gem.files         = `git ls-files`.split("\n")
         
     | 
| 
      
 13 
     | 
    
         
            +
              gem.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
         
     | 
| 
      
 14 
     | 
    
         
            +
              gem.name          = "directiverecord"
         
     | 
| 
      
 15 
     | 
    
         
            +
              gem.require_paths = ["lib"]
         
     | 
| 
      
 16 
     | 
    
         
            +
              gem.version       = DirectiveRecord::VERSION
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              gem.add_dependency "activerecord", ">= 4.0"
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              gem.add_development_dependency "rake"
         
     | 
| 
      
 21 
     | 
    
         
            +
              gem.add_development_dependency "yard"
         
     | 
| 
      
 22 
     | 
    
         
            +
              gem.add_development_dependency "pry"
         
     | 
| 
      
 23 
     | 
    
         
            +
              gem.add_development_dependency "mysql2"
         
     | 
| 
      
 24 
     | 
    
         
            +
              gem.add_development_dependency "simplecov"
         
     | 
| 
      
 25 
     | 
    
         
            +
              gem.add_development_dependency "minitest"
         
     | 
| 
      
 26 
     | 
    
         
            +
              gem.add_development_dependency "mocha"
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "directive_record/gem_ext/active_record"
         
     | 
| 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ActiveRecord
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Relation
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                def qry_options(*args)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  DirectiveRecord::Relation.new(self).qry_options(*args)
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def to_qry(*args)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  klass.to_qry qry_options(*args)
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def qry(*args)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  klass.qry qry_options(*args)
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "directive_record/query/sql"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "directive_record/query/mysql"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "directive_record/query/monetdb"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module DirectiveRecord
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Query
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def self.new(klass)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  class_for(klass.connection.class.name.downcase).new(klass)
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              private
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def self.class_for(connection_class)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  if connection_class.include?("mysql")
         
     | 
| 
      
 16 
     | 
    
         
            +
                    MySQL
         
     | 
| 
      
 17 
     | 
    
         
            +
                  elsif connection_class.include?("monetdb")
         
     | 
| 
      
 18 
     | 
    
         
            +
                    MonetDB
         
     | 
| 
      
 19 
     | 
    
         
            +
                  else
         
     | 
| 
      
 20 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,54 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module DirectiveRecord
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Query
         
     | 
| 
      
 3 
     | 
    
         
            +
                class MonetDB < SQL
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                private
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def path_delimiter
         
     | 
| 
      
 8 
     | 
    
         
            +
                    "__"
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def aggregate_delimiter
         
     | 
| 
      
 12 
     | 
    
         
            +
                    "__"
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def group_by_all_sql
         
     | 
| 
      
 16 
     | 
    
         
            +
                    "all_rows"
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def prepare_options!(options)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    normalize_group_by! options
         
     | 
| 
      
 21 
     | 
    
         
            +
                    [:select, :where, :having, :group_by, :order_by].each do |key|
         
     | 
| 
      
 22 
     | 
    
         
            +
                      if options[key]
         
     | 
| 
      
 23 
     | 
    
         
            +
                        base.reflections.keys.each do |association|
         
     | 
| 
      
 24 
     | 
    
         
            +
                          options[key] = [options[key]].flatten.collect{|x| x.gsub(/^#{association}\.([a-z_\.]+)/) { "#{association}_#{$1.gsub(".", "_")}" }}
         
     | 
| 
      
 25 
     | 
    
         
            +
                        end
         
     | 
| 
      
 26 
     | 
    
         
            +
                      end
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def finalize_options!(options)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    if options[:having]
         
     | 
| 
      
 32 
     | 
    
         
            +
                      if options[:numerize_aliases]
         
     | 
| 
      
 33 
     | 
    
         
            +
                        map = Hash[options[:select].scan(/,?\s?(.*?) AS ([^,]+)/).collect(&:reverse)]
         
     | 
| 
      
 34 
     | 
    
         
            +
                        options[:aliases].each{|pattern, replacement| options[:having].gsub! pattern, map[replacement]}
         
     | 
| 
      
 35 
     | 
    
         
            +
                      else
         
     | 
| 
      
 36 
     | 
    
         
            +
                        (options[:aggregates] || {}).each do |path, aggregate|
         
     | 
| 
      
 37 
     | 
    
         
            +
                          options[:having].gsub! /\b#{aggregate}__#{path}\b/, "#{aggregate.to_s.upcase}(#{path})"
         
     | 
| 
      
 38 
     | 
    
         
            +
                        end
         
     | 
| 
      
 39 
     | 
    
         
            +
                      end
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    options[:select] = options[:select].split(", ").collect do |string|
         
     | 
| 
      
 43 
     | 
    
         
            +
                      expression, query_alias = string.match(/^(.*) AS (.*)$/).try(:captures)
         
     | 
| 
      
 44 
     | 
    
         
            +
                      if query_alias
         
     | 
| 
      
 45 
     | 
    
         
            +
                        options[:group_by].to_s.include?(expression) || !expression.match(/^\w+(\.\w+)*$/) ? string : "MAX(#{expression}) AS #{query_alias}"
         
     | 
| 
      
 46 
     | 
    
         
            +
                      else
         
     | 
| 
      
 47 
     | 
    
         
            +
                        string.match(/^\w+(\.\w+)*$/) ? "MAX(#{string})" : string
         
     | 
| 
      
 48 
     | 
    
         
            +
                      end
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end.join(", ") if options[:group_by]
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module DirectiveRecord
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Query
         
     | 
| 
      
 3 
     | 
    
         
            +
                class MySQL < SQL
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                private
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def path_delimiter
         
     | 
| 
      
 8 
     | 
    
         
            +
                    "."
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def aggregate_delimiter
         
     | 
| 
      
 12 
     | 
    
         
            +
                    ":"
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def group_by_all_sql
         
     | 
| 
      
 16 
     | 
    
         
            +
                    "NULL"
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def quote_alias(sql_alias)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    "`#{sql_alias}`"
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def finalize_options!(options)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    return unless options[:numerize_aliases]
         
     | 
| 
      
 25 
     | 
    
         
            +
                    [:group_by, :having, :order_by].each do |key|
         
     | 
| 
      
 26 
     | 
    
         
            +
                      if sql = options[key]
         
     | 
| 
      
 27 
     | 
    
         
            +
                        options[:aliases].each do |pattern, replacement|
         
     | 
| 
      
 28 
     | 
    
         
            +
                          sql.gsub! pattern, replacement
         
     | 
| 
      
 29 
     | 
    
         
            +
                        end
         
     | 
| 
      
 30 
     | 
    
         
            +
                      end
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,291 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module DirectiveRecord
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Query
         
     | 
| 
      
 3 
     | 
    
         
            +
                class SQL
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                  def initialize(base)
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @base = base
         
     | 
| 
      
 7 
     | 
    
         
            +
                  end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def to_sql(*args)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    options = extract_options(args)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    validate_options! options
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    optimize_query! options
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    prepare_options! options
         
     | 
| 
      
 16 
     | 
    
         
            +
                    normalize_options! options
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    parse_joins! options
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    prepend_base_alias! options
         
     | 
| 
      
 21 
     | 
    
         
            +
                    finalize_options! options
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    compose_sql options
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                private
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def path_delimiter
         
     | 
| 
      
 29 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def aggregate_delimiter
         
     | 
| 
      
 33 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  def group_by_all_sql
         
     | 
| 
      
 37 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  def select_aggregate_sql(method, path)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    "#{method.to_s.upcase}(#{path})"
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  def select_aggregate_sql_alias(method, path)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    quote_alias("#{method}#{aggregate_delimiter}#{path}")
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  def base
         
     | 
| 
      
 49 
     | 
    
         
            +
                    @base
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  def base_alias
         
     | 
| 
      
 53 
     | 
    
         
            +
                    @base_alias ||= quote_alias(base.table_name.split("_").collect{|x| x[0]}.join(""))
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  def quote_alias(sql_alias)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    sql_alias
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  def extract_options(args)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    options = args.extract_options!.deep_dup
         
     | 
| 
      
 62 
     | 
    
         
            +
                    options.reverse_merge! :select => (args.empty? ? "*" : args)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    options
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                  def validate_options!(options)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    options.assert_valid_keys :select, :where, :group_by, :order_by, :limit, :offset, :aggregates, :numerize_aliases
         
     | 
| 
      
 68 
     | 
    
         
            +
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  def optimize_query!(options)
         
     | 
| 
      
 71 
     | 
    
         
            +
                    select = [options[:select]].flatten
         
     | 
| 
      
 72 
     | 
    
         
            +
                    if options[:where] && (select != %w(id)) && select.any?{|x| x.match(/^\w+(\.\w+)+$/)}
         
     | 
| 
      
 73 
     | 
    
         
            +
                      ids = base.connection.select_values(to_sql(options.merge(:select => "id"))).uniq + [0]
         
     | 
| 
      
 74 
     | 
    
         
            +
                      options[:where] = ["id IN (#{ids.join(", ")})"]
         
     | 
| 
      
 75 
     | 
    
         
            +
                      options.delete :limit
         
     | 
| 
      
 76 
     | 
    
         
            +
                      options.delete :offset
         
     | 
| 
      
 77 
     | 
    
         
            +
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  def prepare_options!(options); end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  def normalize_options!(options)
         
     | 
| 
      
 83 
     | 
    
         
            +
                    normalize_select!(options)
         
     | 
| 
      
 84 
     | 
    
         
            +
                    normalize_where!(options)
         
     | 
| 
      
 85 
     | 
    
         
            +
                    normalize_group_by!(options)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    normalize_order_by!(options)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    options.reject!{|k, v| v.blank?}
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  def normalize_select!(options)
         
     | 
| 
      
 91 
     | 
    
         
            +
                    select = to_array! options, :select
         
     | 
| 
      
 92 
     | 
    
         
            +
                    select.uniq!
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                    options[:scales] = select.inject({}) do |hash, sql|
         
     | 
| 
      
 95 
     | 
    
         
            +
                      if scale = column_for(sql).try(:scale)
         
     | 
| 
      
 96 
     | 
    
         
            +
                        hash[sql] = scale
         
     | 
| 
      
 97 
     | 
    
         
            +
                      end
         
     | 
| 
      
 98 
     | 
    
         
            +
                      hash
         
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                    options[:aggregated] = {}
         
     | 
| 
      
 102 
     | 
    
         
            +
                    options[:aliases] = {}
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                    options[:select] = options[:select].inject([]) do |array, path|
         
     | 
| 
      
 105 
     | 
    
         
            +
                      sql, sql_alias = ((path == ".*") ? "#{base_alias}.*" : path), nil
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                      if aggregate_method = (options[:aggregates] || {})[path]
         
     | 
| 
      
 108 
     | 
    
         
            +
                        sql = select_aggregate_sql(aggregate_method, path)
         
     | 
| 
      
 109 
     | 
    
         
            +
                        sql_alias = options[:aggregated][path] = select_aggregate_sql_alias(aggregate_method, path)
         
     | 
| 
      
 110 
     | 
    
         
            +
                      end
         
     | 
| 
      
 111 
     | 
    
         
            +
                      if scale = options[:scales][path]
         
     | 
| 
      
 112 
     | 
    
         
            +
                        sql = "ROUND(#{sql}, #{scale})"
         
     | 
| 
      
 113 
     | 
    
         
            +
                        sql_alias ||= quote_alias(path)
         
     | 
| 
      
 114 
     | 
    
         
            +
                      end
         
     | 
| 
      
 115 
     | 
    
         
            +
                      if options[:numerize_aliases]
         
     | 
| 
      
 116 
     | 
    
         
            +
                        sql = sql.gsub(/ AS .*$/, "")
         
     | 
| 
      
 117 
     | 
    
         
            +
                        sql_alias = options[:aliases][prepend_base_alias(sql_alias || sql)] = "c#{array.size + 1}"
         
     | 
| 
      
 118 
     | 
    
         
            +
                      end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                      array << [sql, sql_alias].compact.join(" AS ")
         
     | 
| 
      
 121 
     | 
    
         
            +
                      array
         
     | 
| 
      
 122 
     | 
    
         
            +
                    end
         
     | 
| 
      
 123 
     | 
    
         
            +
                  end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                  def normalize_where!(options)
         
     | 
| 
      
 126 
     | 
    
         
            +
                    regexp = /^\S+/
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                    where, having = (to_array!(options, :where) || []).partition do |statement|
         
     | 
| 
      
 129 
     | 
    
         
            +
                      !options[:aggregated].keys.include?(statement.strip.match(regexp).to_s) &&
         
     | 
| 
      
 130 
     | 
    
         
            +
                      statement.downcase.gsub(/((?<![\\])['"])((?:.(?!(?<![\\])\1))*.?)\1/, " ")
         
     | 
| 
      
 131 
     | 
    
         
            +
                                        .scan(/([a-zA-Z_\.]+)?\s*(=|<=>|>=|>|<=|<|<>|!=|is|like|rlike|regexp|in|between|not|sounds|soundex)(\b|\s)/)
         
     | 
| 
      
 132 
     | 
    
         
            +
                                        .all? do |(path, operator)|
         
     | 
| 
      
 133 
     | 
    
         
            +
                        path && column_for(path)
         
     | 
| 
      
 134 
     | 
    
         
            +
                      end
         
     | 
| 
      
 135 
     | 
    
         
            +
                    end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                    unless (attrs = base.scope_attributes).blank?
         
     | 
| 
      
 138 
     | 
    
         
            +
                      sql = base.send(:sanitize_sql_for_conditions, attrs, "").gsub(/``.`(\w+)`/) { $1 }
         
     | 
| 
      
 139 
     | 
    
         
            +
                      where << sql
         
     | 
| 
      
 140 
     | 
    
         
            +
                    end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                    options[:where], options[:having] = where, having.collect do |statement|
         
     | 
| 
      
 143 
     | 
    
         
            +
                      statement.strip.gsub(regexp){|path| options[:aggregated][path] || path}
         
     | 
| 
      
 144 
     | 
    
         
            +
                    end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                    [:where, :having].each do |key|
         
     | 
| 
      
 147 
     | 
    
         
            +
                      value = options[key]
         
     | 
| 
      
 148 
     | 
    
         
            +
                      options[key] = (value.collect{|x| "(#{x})"}.join(" AND ") unless value.empty?)
         
     | 
| 
      
 149 
     | 
    
         
            +
                    end
         
     | 
| 
      
 150 
     | 
    
         
            +
                  end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                  def normalize_group_by!(options)
         
     | 
| 
      
 153 
     | 
    
         
            +
                    group_by = to_array! options, :group_by
         
     | 
| 
      
 154 
     | 
    
         
            +
                    group_by.clear.push(group_by_all_sql) if group_by == [:all]
         
     | 
| 
      
 155 
     | 
    
         
            +
                  end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                  def normalize_order_by!(options)
         
     | 
| 
      
 158 
     | 
    
         
            +
                    options[:order_by] ||= (options[:group_by] || []).collect do |path|
         
     | 
| 
      
 159 
     | 
    
         
            +
                      direction = "DESC" if path.to_s == "date"
         
     | 
| 
      
 160 
     | 
    
         
            +
                      "#{path} #{direction}".strip
         
     | 
| 
      
 161 
     | 
    
         
            +
                    end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                    to_array!(options, :order_by).collect! do |x|
         
     | 
| 
      
 164 
     | 
    
         
            +
                      segments = x.split " "
         
     | 
| 
      
 165 
     | 
    
         
            +
                      direction = segments.pop if %w(asc desc).include?(segments[-1].downcase)
         
     | 
| 
      
 166 
     | 
    
         
            +
                      path = segments.join " "
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                      if path != group_by_all_sql
         
     | 
| 
      
 169 
     | 
    
         
            +
                        scale = options[:scales][path]
         
     | 
| 
      
 170 
     | 
    
         
            +
                        select = begin
         
     | 
| 
      
 171 
     | 
    
         
            +
                          if aggregate_method = (options[:aggregates] || {})[path]
         
     | 
| 
      
 172 
     | 
    
         
            +
                            select_aggregate_sql(aggregate_method, path)
         
     | 
| 
      
 173 
     | 
    
         
            +
                          else
         
     | 
| 
      
 174 
     | 
    
         
            +
                            path
         
     | 
| 
      
 175 
     | 
    
         
            +
                          end
         
     | 
| 
      
 176 
     | 
    
         
            +
                        end
         
     | 
| 
      
 177 
     | 
    
         
            +
                        "#{scale ? "ROUND(#{select}, #{scale})" : select} #{direction.upcase if direction}".strip
         
     | 
| 
      
 178 
     | 
    
         
            +
                      end
         
     | 
| 
      
 179 
     | 
    
         
            +
                    end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                    options[:order_by].compact!
         
     | 
| 
      
 182 
     | 
    
         
            +
                  end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                  def to_array!(options, key)
         
     | 
| 
      
 185 
     | 
    
         
            +
                    if value = options[key]
         
     | 
| 
      
 186 
     | 
    
         
            +
                      options[key] = [value].flatten
         
     | 
| 
      
 187 
     | 
    
         
            +
                    end
         
     | 
| 
      
 188 
     | 
    
         
            +
                  end
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                  def column_for(path)
         
     | 
| 
      
 191 
     | 
    
         
            +
                    segments = path.split(".")
         
     | 
| 
      
 192 
     | 
    
         
            +
                    column = segments.pop
         
     | 
| 
      
 193 
     | 
    
         
            +
                    model = segments.inject(base) do |klass, association|
         
     | 
| 
      
 194 
     | 
    
         
            +
                      klass.reflect_on_association(association.to_sym).klass
         
     | 
| 
      
 195 
     | 
    
         
            +
                    end
         
     | 
| 
      
 196 
     | 
    
         
            +
                    model.columns_hash[column]
         
     | 
| 
      
 197 
     | 
    
         
            +
                  rescue
         
     | 
| 
      
 198 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 199 
     | 
    
         
            +
                  end
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
      
 201 
     | 
    
         
            +
                  def parse_joins!(options)
         
     | 
| 
      
 202 
     | 
    
         
            +
                    return if (paths = extract_paths(options)).empty?
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
                    regexp = /INNER JOIN `([^`]+)`( `[^`]+`)? ON `[^`]+`.`([^`]+)` = `[^`]+`.`([^`]+)`/
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
                    options[:joins] = paths.collect do |path|
         
     | 
| 
      
 207 
     | 
    
         
            +
                      joins, associations = [], []
         
     | 
| 
      
 208 
     | 
    
         
            +
                      path.split(".").inject(base) do |klass, association|
         
     | 
| 
      
 209 
     | 
    
         
            +
                        association = association.to_sym
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                        table_joins = klass.joins(association).to_sql.scan regexp
         
     | 
| 
      
 212 
     | 
    
         
            +
                        concerns_bridge_table = table_joins.size == 2
         
     | 
| 
      
 213 
     | 
    
         
            +
                        bridge_table_as = nil
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
                        table_joins.each_with_index do |table_join, index|
         
     | 
| 
      
 216 
     | 
    
         
            +
                          concerns_bridge_table_join = concerns_bridge_table && index == 0
         
     | 
| 
      
 217 
     | 
    
         
            +
                          join_table, possible_alias, join_table_column, table_column = table_join
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                          table_as = (klass == base) ? base_alias : quote_alias(associations.join(path_delimiter))
         
     | 
| 
      
 220 
     | 
    
         
            +
                          join_table_as = quote_alias((associations + [association]).join(path_delimiter))
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                          if concerns_bridge_table
         
     | 
| 
      
 223 
     | 
    
         
            +
                            if concerns_bridge_table_join
         
     | 
| 
      
 224 
     | 
    
         
            +
                              join_table_as = bridge_table_as = quote_alias("#{(associations + [association]).join(path_delimiter)}_bridge_table")
         
     | 
| 
      
 225 
     | 
    
         
            +
                            else
         
     | 
| 
      
 226 
     | 
    
         
            +
                              table_as = bridge_table_as
         
     | 
| 
      
 227 
     | 
    
         
            +
                            end
         
     | 
| 
      
 228 
     | 
    
         
            +
                          end
         
     | 
| 
      
 229 
     | 
    
         
            +
             
     | 
| 
      
 230 
     | 
    
         
            +
                          joins.push "LEFT JOIN #{join_table} #{join_table_as} ON #{join_table_as}.#{join_table_column} = #{table_as}.#{table_column}"
         
     | 
| 
      
 231 
     | 
    
         
            +
                        end
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
                        associations << association
         
     | 
| 
      
 234 
     | 
    
         
            +
                        klass.reflect_on_association(association).klass
         
     | 
| 
      
 235 
     | 
    
         
            +
                      end
         
     | 
| 
      
 236 
     | 
    
         
            +
                      joins
         
     | 
| 
      
 237 
     | 
    
         
            +
                    end.flatten.uniq.join("\n")
         
     | 
| 
      
 238 
     | 
    
         
            +
                  end
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
                  def extract_paths(options)
         
     | 
| 
      
 241 
     | 
    
         
            +
                    [:select, :where, :group_by, :having, :order_by].inject([]) do |paths, key|
         
     | 
| 
      
 242 
     | 
    
         
            +
                      if value = options[key]
         
     | 
| 
      
 243 
     | 
    
         
            +
                        value = value.join " " if value.is_a?(Array)
         
     | 
| 
      
 244 
     | 
    
         
            +
                        paths.concat value.gsub(/((?<![\\])['"])((?:.(?!(?<![\\])\1))*.?)\1/, " ").scan(/[a-zA-Z_]+\.[a-zA-Z_\.]+/).collect{|x| x.split(".")[0..-2].join "."}
         
     | 
| 
      
 245 
     | 
    
         
            +
                      else
         
     | 
| 
      
 246 
     | 
    
         
            +
                        paths
         
     | 
| 
      
 247 
     | 
    
         
            +
                      end
         
     | 
| 
      
 248 
     | 
    
         
            +
                    end.uniq
         
     | 
| 
      
 249 
     | 
    
         
            +
                  end
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
                  def prepend_base_alias!(options)
         
     | 
| 
      
 252 
     | 
    
         
            +
                    [:select, :where, :group_by, :having, :order_by].each do |key|
         
     | 
| 
      
 253 
     | 
    
         
            +
                      if value = options[key]
         
     | 
| 
      
 254 
     | 
    
         
            +
                        options[key] = prepend_base_alias value, options[:aliases]
         
     | 
| 
      
 255 
     | 
    
         
            +
                      end
         
     | 
| 
      
 256 
     | 
    
         
            +
                    end
         
     | 
| 
      
 257 
     | 
    
         
            +
                  end
         
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
                  def prepend_base_alias(sql, aliases = {})
         
     | 
| 
      
 260 
     | 
    
         
            +
                    columns = base.columns_hash.keys
         
     | 
| 
      
 261 
     | 
    
         
            +
                    sql = sql.join ", " if sql.is_a?(Array)
         
     | 
| 
      
 262 
     | 
    
         
            +
                    sql.gsub(/("[^"]*"|'[^']*'|`[^`]*`|[a-zA-Z_#{aggregate_delimiter}]+(\.[a-zA-Z_\*]+)*)/) do
         
     | 
| 
      
 263 
     | 
    
         
            +
                      columns.include?($1) ? "#{base_alias}.#{$1}" : begin
         
     | 
| 
      
 264 
     | 
    
         
            +
                        if (string = $1).match /^([a-zA-Z_\.]+)\.([a-zA-Z_\*]+)$/
         
     | 
| 
      
 265 
     | 
    
         
            +
                          path, column = $1, $2
         
     | 
| 
      
 266 
     | 
    
         
            +
                          "#{quote_alias path.gsub(".", path_delimiter)}.#{column}"
         
     | 
| 
      
 267 
     | 
    
         
            +
                        else
         
     | 
| 
      
 268 
     | 
    
         
            +
                          string
         
     | 
| 
      
 269 
     | 
    
         
            +
                        end
         
     | 
| 
      
 270 
     | 
    
         
            +
                      end
         
     | 
| 
      
 271 
     | 
    
         
            +
                    end
         
     | 
| 
      
 272 
     | 
    
         
            +
                  end
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
                  def finalize_options!(options); end
         
     | 
| 
      
 275 
     | 
    
         
            +
             
     | 
| 
      
 276 
     | 
    
         
            +
                  def compose_sql(options)
         
     | 
| 
      
 277 
     | 
    
         
            +
                    sql = ["SELECT #{options[:select]}", "FROM #{base.table_name} #{base_alias}", options[:joins]].compact
         
     | 
| 
      
 278 
     | 
    
         
            +
             
     | 
| 
      
 279 
     | 
    
         
            +
                    [:where, :group_by, :having, :order_by, :limit, :offset].each do |key|
         
     | 
| 
      
 280 
     | 
    
         
            +
                      unless (value = options[key]).blank?
         
     | 
| 
      
 281 
     | 
    
         
            +
                        keyword = key.to_s.upcase.gsub("_", " ")
         
     | 
| 
      
 282 
     | 
    
         
            +
                        sql << "#{keyword} #{value}"
         
     | 
| 
      
 283 
     | 
    
         
            +
                      end
         
     | 
| 
      
 284 
     | 
    
         
            +
                    end
         
     | 
| 
      
 285 
     | 
    
         
            +
             
     | 
| 
      
 286 
     | 
    
         
            +
                    sql.join "\n"
         
     | 
| 
      
 287 
     | 
    
         
            +
                  end
         
     | 
| 
      
 288 
     | 
    
         
            +
             
     | 
| 
      
 289 
     | 
    
         
            +
                end
         
     | 
| 
      
 290 
     | 
    
         
            +
              end
         
     | 
| 
      
 291 
     | 
    
         
            +
            end
         
     |