maksar-meta_where 1.0.4
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.
- data/.document +5 -0
 - data/.gitignore +21 -0
 - data/CHANGELOG +90 -0
 - data/Gemfile +8 -0
 - data/LICENSE +20 -0
 - data/README.rdoc +343 -0
 - data/Rakefile +11 -0
 - data/lib/core_ext/hash.rb +5 -0
 - data/lib/core_ext/symbol.rb +39 -0
 - data/lib/core_ext/symbol_operators.rb +48 -0
 - data/lib/meta_where.rb +51 -0
 - data/lib/meta_where/association_reflection.rb +51 -0
 - data/lib/meta_where/column.rb +31 -0
 - data/lib/meta_where/compound.rb +20 -0
 - data/lib/meta_where/condition.rb +32 -0
 - data/lib/meta_where/condition_operators.rb +19 -0
 - data/lib/meta_where/function.rb +108 -0
 - data/lib/meta_where/join_dependency.rb +105 -0
 - data/lib/meta_where/join_type.rb +43 -0
 - data/lib/meta_where/not.rb +13 -0
 - data/lib/meta_where/relation.rb +290 -0
 - data/lib/meta_where/utility.rb +51 -0
 - data/lib/meta_where/version.rb +3 -0
 - data/lib/meta_where/visitors/attribute.rb +58 -0
 - data/lib/meta_where/visitors/predicate.rb +149 -0
 - data/lib/meta_where/visitors/visitor.rb +52 -0
 - data/meta_where.gemspec +48 -0
 - data/test/fixtures/companies.yml +17 -0
 - data/test/fixtures/company.rb +7 -0
 - data/test/fixtures/data_type.rb +3 -0
 - data/test/fixtures/data_types.yml +15 -0
 - data/test/fixtures/developer.rb +5 -0
 - data/test/fixtures/developers.yml +55 -0
 - data/test/fixtures/developers_projects.yml +25 -0
 - data/test/fixtures/fixed_bid_project.rb +2 -0
 - data/test/fixtures/invalid_company.rb +4 -0
 - data/test/fixtures/invalid_developer.rb +4 -0
 - data/test/fixtures/note.rb +3 -0
 - data/test/fixtures/notes.yml +95 -0
 - data/test/fixtures/people.yml +14 -0
 - data/test/fixtures/person.rb +4 -0
 - data/test/fixtures/project.rb +7 -0
 - data/test/fixtures/projects.yml +29 -0
 - data/test/fixtures/schema.rb +53 -0
 - data/test/fixtures/time_and_materials_project.rb +2 -0
 - data/test/helper.rb +33 -0
 - data/test/test_base.rb +21 -0
 - data/test/test_relations.rb +455 -0
 - metadata +173 -0
 
| 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MetaWhere
         
     | 
| 
      
 2 
     | 
    
         
            +
              class JoinType
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :name, :join_type, :klass
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(name, join_type = Arel::Nodes::InnerJoin, klass = nil)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @name = name
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @join_type = join_type
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @klass = klass
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def ==(other)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  self.class == other.class &&
         
     | 
| 
      
 13 
     | 
    
         
            +
                  name == other.name &&
         
     | 
| 
      
 14 
     | 
    
         
            +
                  join_type == other.join_type &&
         
     | 
| 
      
 15 
     | 
    
         
            +
                  klass == other.klass
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                alias_method :eql?, :==
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def hash
         
     | 
| 
      
 21 
     | 
    
         
            +
                  [name, join_type, klass].hash
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def outer
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @join_type = Arel::Nodes::OuterJoin
         
     | 
| 
      
 26 
     | 
    
         
            +
                  self
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def inner
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @join_type = Arel::Nodes::InnerJoin
         
     | 
| 
      
 31 
     | 
    
         
            +
                  self
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def type(klass)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @klass = klass
         
     | 
| 
      
 36 
     | 
    
         
            +
                  self
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def to_sym
         
     | 
| 
      
 40 
     | 
    
         
            +
                  self
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,290 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MetaWhere
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Relation
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                JoinAssociation = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
         
     | 
| 
      
 5 
     | 
    
         
            +
                JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                attr_writer :join_dependency
         
     | 
| 
      
 8 
     | 
    
         
            +
                private :join_dependency=
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def self.included(base)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  base.class_eval do
         
     | 
| 
      
 12 
     | 
    
         
            +
                    alias_method_chain :reset, :metawhere
         
     | 
| 
      
 13 
     | 
    
         
            +
                    alias_method_chain :scope_for_create, :metawhere
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  # We have to do this on the singleton to work with Ruby 1.8.7. Not sure why.
         
     | 
| 
      
 17 
     | 
    
         
            +
                  base.instance_eval do
         
     | 
| 
      
 18 
     | 
    
         
            +
                    alias_method :&, :merge
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def join_dependency
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @join_dependency ||= (build_join_dependency(table.from(table), @joins_values) && @join_dependency)
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def merge(r, association_name = nil)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  if (r && (association_name || base_class.name != r.klass.base_class.name)) # Merging relations with different base.
         
     | 
| 
      
 28 
     | 
    
         
            +
                    association_name ||= (default_association = reflect_on_all_associations.detect {|a| a.class_name == r.klass.name}) ?
         
     | 
| 
      
 29 
     | 
    
         
            +
                                         default_association.name : r.table_name.to_sym
         
     | 
| 
      
 30 
     | 
    
         
            +
                    r = r.clone
         
     | 
| 
      
 31 
     | 
    
         
            +
                    r.where_values.map! {|w| MetaWhere::Visitors::Predicate.visitables.include?(w.class) ? {association_name => w} : w}
         
     | 
| 
      
 32 
     | 
    
         
            +
                    r.joins_values.map! {|j| [Symbol, Hash, MetaWhere::JoinType].include?(j.class) ? {association_name => j} : j}
         
     | 
| 
      
 33 
     | 
    
         
            +
                    self.joins_values += [association_name] if reflect_on_association(association_name)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  super(r)
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def reset_with_metawhere
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @mw_unique_joins = @mw_association_joins = @mw_non_association_joins =
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @mw_stashed_association_joins = @mw_custom_joins = nil
         
     | 
| 
      
 42 
     | 
    
         
            +
                  reset_without_metawhere
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def scope_for_create_with_metawhere
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @scope_for_create ||= begin
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @create_with_value || predicates_without_conflicting_equality.inject({}) do |hash, where|
         
     | 
| 
      
 48 
     | 
    
         
            +
                      if is_equality_predicate?(where)
         
     | 
| 
      
 49 
     | 
    
         
            +
                        hash[where.left.name] = where.right.respond_to?(:value) ? where.right.value : where.right
         
     | 
| 
      
 50 
     | 
    
         
            +
                      end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                      hash
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def build_where(opts, other = [])
         
     | 
| 
      
 58 
     | 
    
         
            +
                  if opts.is_a?(String)
         
     | 
| 
      
 59 
     | 
    
         
            +
                    [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
         
     | 
| 
      
 60 
     | 
    
         
            +
                  else
         
     | 
| 
      
 61 
     | 
    
         
            +
                    predicates = []
         
     | 
| 
      
 62 
     | 
    
         
            +
                    [opts, *other].each do |arg|
         
     | 
| 
      
 63 
     | 
    
         
            +
                      predicates += Array.wrap(
         
     | 
| 
      
 64 
     | 
    
         
            +
                        case arg
         
     | 
| 
      
 65 
     | 
    
         
            +
                        when Array
         
     | 
| 
      
 66 
     | 
    
         
            +
                          @klass.send(:sanitize_sql, arg)
         
     | 
| 
      
 67 
     | 
    
         
            +
                        when Hash
         
     | 
| 
      
 68 
     | 
    
         
            +
                          @klass.send(:expand_hash_conditions_for_aggregates, arg)
         
     | 
| 
      
 69 
     | 
    
         
            +
                        else
         
     | 
| 
      
 70 
     | 
    
         
            +
                          arg
         
     | 
| 
      
 71 
     | 
    
         
            +
                        end
         
     | 
| 
      
 72 
     | 
    
         
            +
                      )
         
     | 
| 
      
 73 
     | 
    
         
            +
                    end
         
     | 
| 
      
 74 
     | 
    
         
            +
                    predicates
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                def predicates_without_conflicting_equality
         
     | 
| 
      
 79 
     | 
    
         
            +
                  remove_conflicting_equality_predicates(flatten_predicates(@where_values, predicate_visitor))
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                # Very occasionally, we need to get a visitor for another relation, so it makes sense to factor
         
     | 
| 
      
 83 
     | 
    
         
            +
                # these out into a public method despite only being two lines long.
         
     | 
| 
      
 84 
     | 
    
         
            +
                def predicate_visitor
         
     | 
| 
      
 85 
     | 
    
         
            +
                  @predicate_visitor ||= begin
         
     | 
| 
      
 86 
     | 
    
         
            +
                    visitor = MetaWhere::Visitors::Predicate.new
         
     | 
| 
      
 87 
     | 
    
         
            +
                    visitor.join_dependency = join_dependency
         
     | 
| 
      
 88 
     | 
    
         
            +
                    visitor
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def attribute_visitor
         
     | 
| 
      
 93 
     | 
    
         
            +
                  @attribute_visitor ||= begin
         
     | 
| 
      
 94 
     | 
    
         
            +
                    visitor = MetaWhere::Visitors::Attribute.new
         
     | 
| 
      
 95 
     | 
    
         
            +
                    visitor.join_dependency = join_dependency
         
     | 
| 
      
 96 
     | 
    
         
            +
                    visitor
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                # Simulate the logic that occurs in ActiveRecord::Relation.to_a
         
     | 
| 
      
 101 
     | 
    
         
            +
                #
         
     | 
| 
      
 102 
     | 
    
         
            +
                # @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql)
         
     | 
| 
      
 103 
     | 
    
         
            +
                #
         
     | 
| 
      
 104 
     | 
    
         
            +
                # This will let us get a dump of the SQL that will be run against the DB for debug
         
     | 
| 
      
 105 
     | 
    
         
            +
                # purposes without actually running the query.
         
     | 
| 
      
 106 
     | 
    
         
            +
                def debug_sql
         
     | 
| 
      
 107 
     | 
    
         
            +
                  if eager_loading?
         
     | 
| 
      
 108 
     | 
    
         
            +
                    including = (@eager_load_values + @includes_values).uniq
         
     | 
| 
      
 109 
     | 
    
         
            +
                    join_dependency = JoinDependency.new(@klass, including, [])
         
     | 
| 
      
 110 
     | 
    
         
            +
                    construct_relation_for_association_find(join_dependency).to_sql
         
     | 
| 
      
 111 
     | 
    
         
            +
                  else
         
     | 
| 
      
 112 
     | 
    
         
            +
                    arel.to_sql
         
     | 
| 
      
 113 
     | 
    
         
            +
                  end
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                def construct_limited_ids_condition(relation)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  visitor = relation.attribute_visitor
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                  relation.order_values.map! {|o| visitor.can_accept?(o) ? visitor.accept(o).to_sql : o}
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                  super
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                def build_arel
         
     | 
| 
      
 125 
     | 
    
         
            +
                  arel = table.from table
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                  build_join_dependency(arel, @joins_values) unless @joins_values.empty?
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                  visitor = predicate_visitor
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  predicate_wheres = flatten_predicates(@where_values.uniq, visitor)
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                  collapse_wheres(arel, (predicate_wheres - ['']).uniq)
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                  arel.having(*flatten_predicates(@having_values, visitor).reject {|h| h.blank?}) unless @having_values.empty?
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                  arel.take(@limit_value) if @limit_value
         
     | 
| 
      
 138 
     | 
    
         
            +
                  arel.skip(@offset_value) if @offset_value
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                  arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                  build_order(arel, attribute_visitor, @order_values) unless @order_values.empty?
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                  build_select(arel, @select_values.uniq)
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                  arel.from(@from_value) if @from_value
         
     | 
| 
      
 147 
     | 
    
         
            +
                  arel.lock(@lock_value) if @lock_value
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  arel
         
     | 
| 
      
 150 
     | 
    
         
            +
                end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                def select(value = Proc.new)
         
     | 
| 
      
 153 
     | 
    
         
            +
                  if MetaWhere::Function === value
         
     | 
| 
      
 154 
     | 
    
         
            +
                    value.table = self.arel_table
         
     | 
| 
      
 155 
     | 
    
         
            +
                  end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                  super
         
     | 
| 
      
 158 
     | 
    
         
            +
                end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                private
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                def is_equality_predicate?(predicate)
         
     | 
| 
      
 163 
     | 
    
         
            +
                  predicate.class == Arel::Nodes::Equality
         
     | 
| 
      
 164 
     | 
    
         
            +
                end
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                def build_join_dependency(manager, joins)
         
     | 
| 
      
 167 
     | 
    
         
            +
                  buckets = joins.group_by do |join|
         
     | 
| 
      
 168 
     | 
    
         
            +
                    case join
         
     | 
| 
      
 169 
     | 
    
         
            +
                    when String
         
     | 
| 
      
 170 
     | 
    
         
            +
                      'string_join'
         
     | 
| 
      
 171 
     | 
    
         
            +
                    when Hash, Symbol, Array, MetaWhere::JoinType
         
     | 
| 
      
 172 
     | 
    
         
            +
                      'association_join'
         
     | 
| 
      
 173 
     | 
    
         
            +
                    when JoinAssociation
         
     | 
| 
      
 174 
     | 
    
         
            +
                      'stashed_join'
         
     | 
| 
      
 175 
     | 
    
         
            +
                    when Arel::Nodes::Join
         
     | 
| 
      
 176 
     | 
    
         
            +
                      'join_node'
         
     | 
| 
      
 177 
     | 
    
         
            +
                    else
         
     | 
| 
      
 178 
     | 
    
         
            +
                      raise 'unknown class: %s' % join.class.name
         
     | 
| 
      
 179 
     | 
    
         
            +
                    end
         
     | 
| 
      
 180 
     | 
    
         
            +
                  end
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                  association_joins         = buckets['association_join'] || []
         
     | 
| 
      
 183 
     | 
    
         
            +
                  stashed_association_joins = buckets['stashed_join'] || []
         
     | 
| 
      
 184 
     | 
    
         
            +
                  join_nodes                = buckets['join_node'] || []
         
     | 
| 
      
 185 
     | 
    
         
            +
                  string_joins              = (buckets['string_join'] || []).map { |x|
         
     | 
| 
      
 186 
     | 
    
         
            +
                    x.strip
         
     | 
| 
      
 187 
     | 
    
         
            +
                  }.uniq
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                  join_list = custom_join_ast(manager, string_joins)
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                  # All of this duplication just to add
         
     | 
| 
      
 192 
     | 
    
         
            +
                  self.join_dependency = JoinDependency.new(
         
     | 
| 
      
 193 
     | 
    
         
            +
                    @klass,
         
     | 
| 
      
 194 
     | 
    
         
            +
                    association_joins,
         
     | 
| 
      
 195 
     | 
    
         
            +
                    join_list
         
     | 
| 
      
 196 
     | 
    
         
            +
                  )
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
                  join_nodes.each do |join|
         
     | 
| 
      
 199 
     | 
    
         
            +
                    join_dependency.table_aliases[join.left.name.downcase] = 1
         
     | 
| 
      
 200 
     | 
    
         
            +
                  end
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
                  join_dependency.graft(*stashed_association_joins)
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
                  @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
                  # FIXME: refactor this to build an AST
         
     | 
| 
      
 207 
     | 
    
         
            +
                  join_dependency.join_associations.each do |association|
         
     | 
| 
      
 208 
     | 
    
         
            +
                    association.join_to(manager)
         
     | 
| 
      
 209 
     | 
    
         
            +
                  end
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                  manager.join_sources.concat join_nodes.uniq
         
     | 
| 
      
 212 
     | 
    
         
            +
                  manager.join_sources.concat join_list
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                  manager
         
     | 
| 
      
 215 
     | 
    
         
            +
                end
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                def build_order(arel, visitor, orders)
         
     | 
| 
      
 218 
     | 
    
         
            +
                  order_attributes = orders.map {|o|
         
     | 
| 
      
 219 
     | 
    
         
            +
                    visitor.can_accept?(o) ? visitor.accept(o, visitor.join_dependency.join_base) : o
         
     | 
| 
      
 220 
     | 
    
         
            +
                  }.flatten.uniq.reject {|o| o.blank?}
         
     | 
| 
      
 221 
     | 
    
         
            +
                  order_attributes.present? ? arel.order(*order_attributes) : arel
         
     | 
| 
      
 222 
     | 
    
         
            +
                end
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                def remove_conflicting_equality_predicates(predicates)
         
     | 
| 
      
 225 
     | 
    
         
            +
                  predicates.reverse.inject([]) { |ary, w|
         
     | 
| 
      
 226 
     | 
    
         
            +
                    unless is_equality_predicate?(w) && ary.any? {|p| is_equality_predicate?(p) && p.operand1.name == w.operand1.name}
         
     | 
| 
      
 227 
     | 
    
         
            +
                      ary << w
         
     | 
| 
      
 228 
     | 
    
         
            +
                    end
         
     | 
| 
      
 229 
     | 
    
         
            +
                    ary
         
     | 
| 
      
 230 
     | 
    
         
            +
                  }.reverse
         
     | 
| 
      
 231 
     | 
    
         
            +
                end
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
                def collapse_wheres(arel, wheres)
         
     | 
| 
      
 234 
     | 
    
         
            +
                  binaries = wheres.grep(Arel::Nodes::Binary)
         
     | 
| 
      
 235 
     | 
    
         
            +
             
     | 
| 
      
 236 
     | 
    
         
            +
                  groups = binaries.group_by do |binary|
         
     | 
| 
      
 237 
     | 
    
         
            +
                    [binary.class, binary.left]
         
     | 
| 
      
 238 
     | 
    
         
            +
                  end
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
                  groups.each do |_, bins|
         
     | 
| 
      
 241 
     | 
    
         
            +
                    test = bins.inject(bins.shift) do |memo, expr|
         
     | 
| 
      
 242 
     | 
    
         
            +
                      memo.or(expr)
         
     | 
| 
      
 243 
     | 
    
         
            +
                    end
         
     | 
| 
      
 244 
     | 
    
         
            +
                    arel = arel.where(test)
         
     | 
| 
      
 245 
     | 
    
         
            +
                  end
         
     | 
| 
      
 246 
     | 
    
         
            +
             
     | 
| 
      
 247 
     | 
    
         
            +
                  (wheres - binaries).each do |where|
         
     | 
| 
      
 248 
     | 
    
         
            +
                    where = Arel.sql(where) if String === where
         
     | 
| 
      
 249 
     | 
    
         
            +
                    arel = arel.where(Arel::Nodes::Grouping.new(where))
         
     | 
| 
      
 250 
     | 
    
         
            +
                  end
         
     | 
| 
      
 251 
     | 
    
         
            +
                  arel
         
     | 
| 
      
 252 
     | 
    
         
            +
                end
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
                def flatten_predicates(predicates, visitor)
         
     | 
| 
      
 255 
     | 
    
         
            +
                  predicates.map {|p|
         
     | 
| 
      
 256 
     | 
    
         
            +
                    visitor.can_accept?(p) ? visitor.accept(p) : p
         
     | 
| 
      
 257 
     | 
    
         
            +
                  }.flatten.uniq
         
     | 
| 
      
 258 
     | 
    
         
            +
                end
         
     | 
| 
      
 259 
     | 
    
         
            +
                #
         
     | 
| 
      
 260 
     | 
    
         
            +
                # def unique_joins
         
     | 
| 
      
 261 
     | 
    
         
            +
                #   @mw_unique_joins ||= @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
         
     | 
| 
      
 262 
     | 
    
         
            +
                # end
         
     | 
| 
      
 263 
     | 
    
         
            +
                #
         
     | 
| 
      
 264 
     | 
    
         
            +
                # def association_joins
         
     | 
| 
      
 265 
     | 
    
         
            +
                #   @mw_association_joins ||= unique_joins.select{|j|
         
     | 
| 
      
 266 
     | 
    
         
            +
                #     [Hash, Array, Symbol, MetaWhere::JoinType].include?(j.class) && !array_of_strings?(j)
         
     | 
| 
      
 267 
     | 
    
         
            +
                #   }
         
     | 
| 
      
 268 
     | 
    
         
            +
                # end
         
     | 
| 
      
 269 
     | 
    
         
            +
                #
         
     | 
| 
      
 270 
     | 
    
         
            +
                # def string_joins
         
     | 
| 
      
 271 
     | 
    
         
            +
                #   @mw_string_joins ||= unique_joins.select { |j| j.is_a? String }
         
     | 
| 
      
 272 
     | 
    
         
            +
                # end
         
     | 
| 
      
 273 
     | 
    
         
            +
                #
         
     | 
| 
      
 274 
     | 
    
         
            +
                # def join_nodes
         
     | 
| 
      
 275 
     | 
    
         
            +
                #   @mw_join_nodes ||= unique_joins.select { |j| j.is_a? Arel::Nodes::Join }
         
     | 
| 
      
 276 
     | 
    
         
            +
                # end
         
     | 
| 
      
 277 
     | 
    
         
            +
                #
         
     | 
| 
      
 278 
     | 
    
         
            +
                # def stashed_association_joins
         
     | 
| 
      
 279 
     | 
    
         
            +
                #   @mw_stashed_association_joins ||= unique_joins.grep(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
         
     | 
| 
      
 280 
     | 
    
         
            +
                # end
         
     | 
| 
      
 281 
     | 
    
         
            +
                #
         
     | 
| 
      
 282 
     | 
    
         
            +
                # def non_association_joins
         
     | 
| 
      
 283 
     | 
    
         
            +
                #   @mw_non_association_joins ||= (unique_joins - association_joins - stashed_association_joins).reject {|j| j.blank?}
         
     | 
| 
      
 284 
     | 
    
         
            +
                # end
         
     | 
| 
      
 285 
     | 
    
         
            +
                #
         
     | 
| 
      
 286 
     | 
    
         
            +
                # def custom_joins
         
     | 
| 
      
 287 
     | 
    
         
            +
                #   @mw_custom_joins ||= custom_join_ast(@klass.arel_table, non_association_joins)
         
     | 
| 
      
 288 
     | 
    
         
            +
                # end
         
     | 
| 
      
 289 
     | 
    
         
            +
              end
         
     | 
| 
      
 290 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,51 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MetaWhere
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Utility
         
     | 
| 
      
 3 
     | 
    
         
            +
                private
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def array_of_activerecords(val)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  val.is_a?(Array) && !val.empty? && val.all? {|v| v.is_a?(ActiveRecord::Base)}
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def association_from_parent_and_column(parent, column)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  parent.is_a?(Symbol) ? nil : @join_dependency.send(:find_join_association, column, parent)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def attribute_from_column_and_table(column, table)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  case column
         
     | 
| 
      
 15 
     | 
    
         
            +
                  when String, Symbol
         
     | 
| 
      
 16 
     | 
    
         
            +
                    table[column]
         
     | 
| 
      
 17 
     | 
    
         
            +
                  when MetaWhere::Function
         
     | 
| 
      
 18 
     | 
    
         
            +
                    column.table = table
         
     | 
| 
      
 19 
     | 
    
         
            +
                    column.to_sqlliteral
         
     | 
| 
      
 20 
     | 
    
         
            +
                  else
         
     | 
| 
      
 21 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def args_for_predicate(value)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  case value
         
     | 
| 
      
 27 
     | 
    
         
            +
                  when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
         
     | 
| 
      
 28 
     | 
    
         
            +
                    value.to_a.map { |x|
         
     | 
| 
      
 29 
     | 
    
         
            +
                      x.respond_to?(:quoted_id) ? x.quoted_id : x
         
     | 
| 
      
 30 
     | 
    
         
            +
                    }
         
     | 
| 
      
 31 
     | 
    
         
            +
                  when ActiveRecord::Base
         
     | 
| 
      
 32 
     | 
    
         
            +
                    value.quoted_id
         
     | 
| 
      
 33 
     | 
    
         
            +
                  else
         
     | 
| 
      
 34 
     | 
    
         
            +
                    value
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def method_from_value(value)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  case value
         
     | 
| 
      
 40 
     | 
    
         
            +
                  when Array, Range, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation, Arel::Relation
         
     | 
| 
      
 41 
     | 
    
         
            +
                    :in
         
     | 
| 
      
 42 
     | 
    
         
            +
                  else
         
     | 
| 
      
 43 
     | 
    
         
            +
                    :eq
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def valid_comparison_method?(method)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  MetaWhere::PREDICATES.map(&:to_s).include?(method.to_s)
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'meta_where/visitors/visitor'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module MetaWhere
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Visitors
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Attribute < Visitor
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def self.visitables
         
     | 
| 
      
 8 
     | 
    
         
            +
                    [Hash, Symbol, MetaWhere::Column]
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def visit_Hash(o, parent)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    parent = parent.name if parent.is_a? MetaWhere::JoinType
         
     | 
| 
      
 13 
     | 
    
         
            +
                    table = tables[parent]
         
     | 
| 
      
 14 
     | 
    
         
            +
                    built_attributes = o.map do |column, value|
         
     | 
| 
      
 15 
     | 
    
         
            +
                      if value.is_a?(Hash)
         
     | 
| 
      
 16 
     | 
    
         
            +
                        association = association_from_parent_and_column(parent, column)
         
     | 
| 
      
 17 
     | 
    
         
            +
                        accept(value, association || column)
         
     | 
| 
      
 18 
     | 
    
         
            +
                      elsif value.is_a?(Array) && value.all? {|v| can_accept?(v)}
         
     | 
| 
      
 19 
     | 
    
         
            +
                        association = association_from_parent_and_column(parent, column)
         
     | 
| 
      
 20 
     | 
    
         
            +
                        value.map {|val| self.accept(val, association || column)}
         
     | 
| 
      
 21 
     | 
    
         
            +
                      else
         
     | 
| 
      
 22 
     | 
    
         
            +
                        association = association_from_parent_and_column(parent, column)
         
     | 
| 
      
 23 
     | 
    
         
            +
                        can_accept?(value) ? self.accept(value, association || column) : value
         
     | 
| 
      
 24 
     | 
    
         
            +
                      end
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    built_attributes.flatten
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def visit_Symbol(o, parent)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    table = tables[parent]
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    unless attribute = table[o]
         
     | 
| 
      
 34 
     | 
    
         
            +
                      raise ::ActiveRecord::StatementInvalid, "No attribute named `#{o}` exists for table `#{table.name}`"
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    attribute
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  def visit_MetaWhere_Column(o, parent)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    column_name = o.column.to_s
         
     | 
| 
      
 42 
     | 
    
         
            +
                    if column_name.include?('.')
         
     | 
| 
      
 43 
     | 
    
         
            +
                      table_name, column_name = column_name.split('.', 2)
         
     | 
| 
      
 44 
     | 
    
         
            +
                      table = Arel::Table.new(table_name, :engine => parent.arel_engine)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    else
         
     | 
| 
      
 46 
     | 
    
         
            +
                      table = tables[parent]
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    unless attribute = table[column_name]
         
     | 
| 
      
 50 
     | 
    
         
            +
                      raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column_name}` exists for table `#{table.name}`"
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    attribute.send(o.method)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
            end
         
     |