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.
Files changed (49) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/CHANGELOG +90 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +343 -0
  7. data/Rakefile +11 -0
  8. data/lib/core_ext/hash.rb +5 -0
  9. data/lib/core_ext/symbol.rb +39 -0
  10. data/lib/core_ext/symbol_operators.rb +48 -0
  11. data/lib/meta_where.rb +51 -0
  12. data/lib/meta_where/association_reflection.rb +51 -0
  13. data/lib/meta_where/column.rb +31 -0
  14. data/lib/meta_where/compound.rb +20 -0
  15. data/lib/meta_where/condition.rb +32 -0
  16. data/lib/meta_where/condition_operators.rb +19 -0
  17. data/lib/meta_where/function.rb +108 -0
  18. data/lib/meta_where/join_dependency.rb +105 -0
  19. data/lib/meta_where/join_type.rb +43 -0
  20. data/lib/meta_where/not.rb +13 -0
  21. data/lib/meta_where/relation.rb +290 -0
  22. data/lib/meta_where/utility.rb +51 -0
  23. data/lib/meta_where/version.rb +3 -0
  24. data/lib/meta_where/visitors/attribute.rb +58 -0
  25. data/lib/meta_where/visitors/predicate.rb +149 -0
  26. data/lib/meta_where/visitors/visitor.rb +52 -0
  27. data/meta_where.gemspec +48 -0
  28. data/test/fixtures/companies.yml +17 -0
  29. data/test/fixtures/company.rb +7 -0
  30. data/test/fixtures/data_type.rb +3 -0
  31. data/test/fixtures/data_types.yml +15 -0
  32. data/test/fixtures/developer.rb +5 -0
  33. data/test/fixtures/developers.yml +55 -0
  34. data/test/fixtures/developers_projects.yml +25 -0
  35. data/test/fixtures/fixed_bid_project.rb +2 -0
  36. data/test/fixtures/invalid_company.rb +4 -0
  37. data/test/fixtures/invalid_developer.rb +4 -0
  38. data/test/fixtures/note.rb +3 -0
  39. data/test/fixtures/notes.yml +95 -0
  40. data/test/fixtures/people.yml +14 -0
  41. data/test/fixtures/person.rb +4 -0
  42. data/test/fixtures/project.rb +7 -0
  43. data/test/fixtures/projects.yml +29 -0
  44. data/test/fixtures/schema.rb +53 -0
  45. data/test/fixtures/time_and_materials_project.rb +2 -0
  46. data/test/helper.rb +33 -0
  47. data/test/test_base.rb +21 -0
  48. data/test/test_relations.rb +455 -0
  49. metadata +173 -0
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+ require 'rake/testtask'
4
+
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ Rake::TestTask.new(:test) do |test|
8
+ test.libs << 'test'
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,5 @@
1
+ require 'meta_where/condition_operators'
2
+
3
+ class Hash
4
+ include MetaWhere::ConditionOperators
5
+ end
@@ -0,0 +1,39 @@
1
+ class Symbol
2
+ MetaWhere::PREDICATES.each do |predication|
3
+ define_method(predication) do
4
+ MetaWhere::Column.new(self, predication)
5
+ end
6
+ end
7
+
8
+ MetaWhere::METHOD_ALIASES.each_pair do |aliased, predication|
9
+ define_method(aliased) do
10
+ MetaWhere::Column.new(self, predication)
11
+ end
12
+ end
13
+
14
+ def mw_func(*args)
15
+ MetaWhere::Function.new(self, *args)
16
+ end
17
+
18
+ alias_method :func, :mw_func unless method_defined?(:func)
19
+
20
+ def inner
21
+ MetaWhere::JoinType.new(self, Arel::Nodes::InnerJoin)
22
+ end
23
+
24
+ def outer
25
+ MetaWhere::JoinType.new(self, Arel::Nodes::OuterJoin)
26
+ end
27
+
28
+ def type(klass)
29
+ MetaWhere::JoinType.new(self, Arel::Nodes::InnerJoin, klass)
30
+ end
31
+
32
+ def asc
33
+ MetaWhere::Column.new(self, :asc)
34
+ end
35
+
36
+ def desc
37
+ MetaWhere::Column.new(self, :desc)
38
+ end
39
+ end
@@ -0,0 +1,48 @@
1
+ class Symbol
2
+ def [](*values)
3
+ MetaWhere::Function.new(self, *values)
4
+ end
5
+
6
+ def >>(value)
7
+ MetaWhere::Condition.new(self, value, :eq)
8
+ end
9
+
10
+ def ^(value)
11
+ MetaWhere::Condition.new(self, value, :not_eq)
12
+ end
13
+
14
+ def +(value)
15
+ MetaWhere::Condition.new(self, value, :in)
16
+ end
17
+
18
+ def -(value)
19
+ MetaWhere::Condition.new(self, value, :not_in)
20
+ end
21
+
22
+ def =~(value)
23
+ MetaWhere::Condition.new(self, value, :matches)
24
+ end
25
+
26
+ # Won't work on Ruby 1.8.x so need to do this conditionally
27
+ if respond_to?('!~')
28
+ define_method('!~') do |value|
29
+ MetaWhere::Condition.new(self, value, :does_not_match)
30
+ end
31
+ end
32
+
33
+ def >(value)
34
+ MetaWhere::Condition.new(self, value, :gt)
35
+ end
36
+
37
+ def >=(value)
38
+ MetaWhere::Condition.new(self, value, :gteq)
39
+ end
40
+
41
+ def <(value)
42
+ MetaWhere::Condition.new(self, value, :lt)
43
+ end
44
+
45
+ def <=(value)
46
+ MetaWhere::Condition.new(self, value, :lteq)
47
+ end
48
+ end
@@ -0,0 +1,51 @@
1
+ module MetaWhere
2
+ METHOD_ALIASES = {
3
+ 'ne' => :not_eq,
4
+ 'like' => :matches,
5
+ 'not_matches' => :does_not_match,
6
+ 'nlike' => :does_not_match,
7
+ 'lte' => :lteq,
8
+ 'gte' => :gteq,
9
+ 'nin' => :not_in
10
+ }
11
+
12
+ PREDICATES = [
13
+ :eq, :eq_any, :eq_all,
14
+ :not_eq, :not_eq_any, :not_eq_all,
15
+ :matches, :matches_any, :matches_all,
16
+ :does_not_match, :does_not_match_any, :does_not_match_all,
17
+ :lt, :lt_any, :lt_all,
18
+ :lteq, :lteq_any, :lteq_all,
19
+ :gt, :gt_any, :gt_all,
20
+ :gteq, :gteq_any, :gteq_all,
21
+ :in, :in_any, :in_all,
22
+ :not_in, :not_in_any, :not_in_all
23
+ ]
24
+
25
+ def self.operator_overload!
26
+ require 'core_ext/symbol_operators'
27
+ end
28
+ end
29
+
30
+ require 'arel'
31
+ require 'active_record'
32
+ require 'active_support'
33
+
34
+ # TODO: Trim this down, as the individual files should be
35
+ # handling their requires.
36
+ require 'meta_where/column'
37
+ require 'meta_where/condition'
38
+ require 'meta_where/not'
39
+ require 'meta_where/compound'
40
+ require 'meta_where/function'
41
+ require 'meta_where/join_type'
42
+ require 'core_ext/symbol'
43
+ require 'core_ext/hash'
44
+ require 'meta_where/visitors/attribute'
45
+ require 'meta_where/visitors/predicate'
46
+ require 'meta_where/association_reflection'
47
+ require 'meta_where/relation'
48
+ require 'meta_where/join_dependency'
49
+ ActiveRecord::Relation.send(:include, MetaWhere::Relation)
50
+ ActiveRecord::Reflection::AssociationReflection.send(:include, MetaWhere::AssociationReflection)
51
+ ActiveRecord::Associations::ClassMethods::JoinDependency.send(:include, MetaWhere::JoinDependency)
@@ -0,0 +1,51 @@
1
+ module MetaWhere
2
+ class MetaWhereInAssociationError < StandardError; end
3
+
4
+ module AssociationReflection
5
+
6
+ def initialize(macro, name, options, active_record)
7
+ super
8
+
9
+ if options.has_key?(:conditions)
10
+ ensure_no_metawhere_in_conditions(options[:conditions])
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def ensure_no_metawhere_in_conditions(obj)
17
+ case obj
18
+ when Hash
19
+ if obj.keys.grep(MetaWhere::Column).any?
20
+ raise MetaWhereInAssociationError, <<END
21
+ The :#{name} association has a MetaWhere::Column in its :conditions. \
22
+ If you actually needed to access conditions other than equality, then you most \
23
+ likely meant to set up a scope or method, instead. Associations only work with \
24
+ standard equality conditions, since they can be used to create records as well.
25
+ END
26
+ end
27
+
28
+ obj.values.each do |v|
29
+ case v
30
+ when MetaWhere::Condition, Array, Hash
31
+ ensure_no_metawhere_in_conditions(v)
32
+ end
33
+ end
34
+ when Array
35
+ obj.each do |v|
36
+ case v
37
+ when MetaWhere::Condition, Array, Hash
38
+ ensure_no_metawhere_in_conditions(v)
39
+ end
40
+ end
41
+ when MetaWhere::Condition
42
+ raise MetaWhereInAssociationError, <<END
43
+ The :#{name} association has a MetaWhere::Condition in its :conditions. \
44
+ If you actually needed to access conditions other than equality, then you most \
45
+ likely meant to set up a scope or method, instead. Associations only work with \
46
+ standard equality conditions, since they can be used to create records as well.
47
+ END
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,31 @@
1
+ module MetaWhere
2
+ class Column
3
+ attr_reader :column, :method
4
+
5
+ def initialize(column, method)
6
+ @column = column
7
+ @method = method
8
+ end
9
+
10
+ def %(value)
11
+ MetaWhere::Condition.new(column, value, method)
12
+ end
13
+
14
+ def ==(other_column)
15
+ other_column.is_a?(Column) &&
16
+ other_column.column == column &&
17
+ other_column.method == method
18
+ end
19
+
20
+ alias_method :eql?, :==
21
+
22
+ def hash
23
+ [column, method].hash
24
+ end
25
+
26
+ # Play nicely with expand_hash_conditions_for_aggregates
27
+ def to_sym
28
+ "#{column}.#{method}".to_sym
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ require 'meta_where/condition_operators'
2
+
3
+ module MetaWhere
4
+ class Compound
5
+ include ConditionOperators
6
+
7
+ attr_reader :condition1, :condition2
8
+
9
+ def initialize(condition1, condition2)
10
+ @condition1 = condition1
11
+ @condition2 = condition2
12
+ end
13
+ end
14
+
15
+ class Or < Compound
16
+ end
17
+
18
+ class And < Compound
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ require 'meta_where/utility'
2
+ require 'meta_where/condition_operators'
3
+
4
+ module MetaWhere
5
+ class Condition
6
+ include ConditionOperators
7
+ include Utility
8
+
9
+ attr_reader :column, :value, :method
10
+
11
+ def initialize(column, value, method)
12
+ @column = column
13
+ @value = value
14
+ @method = (MetaWhere::METHOD_ALIASES[method.to_s] || method).to_s
15
+ end
16
+
17
+ def ==(other_condition)
18
+ other_condition.is_a?(Condition) &&
19
+ other_condition.column == column &&
20
+ other_condition.value == value &&
21
+ other_condition.method == method
22
+ end
23
+
24
+ alias_method :eql?, :==
25
+
26
+ # Play "nicely" with expand_hash_conditions_for_aggregates
27
+ def to_sym
28
+ self
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,19 @@
1
+ module MetaWhere
2
+ module ConditionOperators
3
+ def |(other)
4
+ Or.new(self, other)
5
+ end
6
+
7
+ def &(other)
8
+ And.new(self, other)
9
+ end
10
+
11
+ def -(other)
12
+ And.new(self, Not.new(other))
13
+ end
14
+
15
+ def -@
16
+ Not.new(self)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,108 @@
1
+ module MetaWhere
2
+ class Function
3
+ attr_reader :name, :args
4
+ attr_accessor :table, :alias
5
+
6
+ def initialize(name, *args)
7
+ @name = name
8
+ @args = args
9
+ end
10
+
11
+ def as(val)
12
+ self.alias = val
13
+ self
14
+ end
15
+
16
+ def to_sqlliteral
17
+ Arel.sql(
18
+ ("#{name}(#{(['%s'] * args.size).join(',')})" % contextualize_args) +
19
+ (self.alias ? " AS #{self.alias}" : '')
20
+ )
21
+ end
22
+
23
+ alias_method :to_sql, :to_sqlliteral
24
+
25
+ MetaWhere::PREDICATES.each do |predication|
26
+ define_method(predication) do
27
+ MetaWhere::Column.new(self, predication)
28
+ end
29
+ end
30
+
31
+ MetaWhere::METHOD_ALIASES.each_pair do |aliased, predication|
32
+ define_method(aliased) do
33
+ MetaWhere::Column.new(self, predication)
34
+ end
35
+ end
36
+
37
+ def >>(value)
38
+ MetaWhere::Condition.new(self, value, :eq)
39
+ end
40
+
41
+ def ^(value)
42
+ MetaWhere::Condition.new(self, value, :not_eq)
43
+ end
44
+
45
+ def +(value)
46
+ MetaWhere::Condition.new(self, value, :in)
47
+ end
48
+
49
+ def -(value)
50
+ MetaWhere::Condition.new(self, value, :not_in)
51
+ end
52
+
53
+ def =~(value)
54
+ MetaWhere::Condition.new(self, value, :matches)
55
+ end
56
+
57
+ # Won't work on Ruby 1.8.x so need to do this conditionally
58
+ if respond_to?('!~')
59
+ define_method('!~') do |value|
60
+ MetaWhere::Condition.new(self, value, :does_not_match)
61
+ end
62
+ end
63
+
64
+ def >(value)
65
+ MetaWhere::Condition.new(self, value, :gt)
66
+ end
67
+
68
+ def >=(value)
69
+ MetaWhere::Condition.new(self, value, :gteq)
70
+ end
71
+
72
+ def <(value)
73
+ MetaWhere::Condition.new(self, value, :lt)
74
+ end
75
+
76
+ def <=(value)
77
+ MetaWhere::Condition.new(self, value, :lteq)
78
+ end
79
+
80
+ # Play "nicely" with expand_hash_conditions_for_aggregates
81
+ def to_sym
82
+ self
83
+ end
84
+
85
+ private
86
+
87
+ def contextualize_args
88
+ args.map do |arg|
89
+ if arg.is_a? Symbol
90
+ self.table && self.table[arg] ? Arel::Visitors.for(ActiveRecord::Base).accept(table[arg]) : arg
91
+ else
92
+ arg.table = self.table if arg.is_a? MetaWhere::Function
93
+ Arel::Visitors.for(ActiveRecord::Base).accept(arg)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ module Arel
101
+ module Visitors
102
+ class ToSql
103
+ def visit_MetaWhere_Function o
104
+ o.to_sqlliteral
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,105 @@
1
+ module MetaWhere
2
+ module JoinDependency
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ alias_method_chain :build, :metawhere
7
+ alias_method_chain :find_join_association, :metawhere
8
+ end
9
+ end
10
+
11
+ class BaseMismatchError < StandardError; end
12
+ class ConfigurationError < StandardError; end
13
+ class AssociationNotFoundError < StandardError; end
14
+
15
+ def build_with_metawhere(associations, parent = nil, join_type = Arel::InnerJoin)
16
+ parent ||= join_parts.last
17
+ if MetaWhere::JoinType === associations
18
+ klass = associations.klass
19
+ join_type = associations.join_type
20
+ associations = associations.name
21
+ end
22
+
23
+ case associations
24
+ when Symbol, String
25
+ reflection = parent.reflections[associations.to_s.intern] or
26
+ raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
27
+ unless (association = find_join_association(reflection, parent)) && (!klass || association.active_record == klass)
28
+ @reflections << reflection
29
+ if reflection.options[:polymorphic]
30
+ raise ArgumentError, "You can't create a polymorphic belongs_to join without specifying the polymorphic class!" unless klass
31
+ association = PolymorphicJoinAssociation.new(reflection, self, klass, parent)
32
+ else
33
+ association = build_join_association(reflection, parent)
34
+ end
35
+ association.join_type = join_type
36
+ @join_parts << association
37
+ cache_joined_association(association)
38
+ end
39
+ association
40
+ else
41
+ build_without_metawhere(associations, parent, join_type)
42
+ end
43
+ end
44
+
45
+ def find_join_association_with_metawhere(name_or_reflection, parent)
46
+ case name_or_reflection
47
+ when MetaWhere::JoinType
48
+ join_associations.detect do |j|
49
+ (j.reflection.name == name_or_reflection.name) &&
50
+ (j.reflection.klass == name_or_reflection.klass) &&
51
+ (j.parent == parent)
52
+ end
53
+ else
54
+ case name_or_reflection
55
+ when Symbol, String
56
+ join_associations.detect {|j| (j.reflection.name == name_or_reflection.to_s.intern) && (j.parent == parent)}
57
+ else
58
+ join_associations.detect {|j| (j.reflection == name_or_reflection) && (j.parent == parent)}
59
+ end
60
+ #find_join_association_without_metawhere(name_or_reflection, parent)
61
+ end
62
+ end
63
+ end
64
+
65
+ class PolymorphicJoinAssociation < ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
66
+
67
+ def initialize(reflection, join_dependency, polymorphic_class, parent = nil)
68
+ reflection.check_validity!
69
+ @active_record = polymorphic_class
70
+ @cached_record = {}
71
+ @column_names_with_alias = nil
72
+ @reflection = reflection.clone
73
+ @reflection.instance_variable_set :"@klass", polymorphic_class
74
+ @join_dependency = join_dependency
75
+ @parent = parent
76
+ @join_type = Arel::InnerJoin
77
+ @aliased_prefix = "t#{ join_dependency.join_parts.size }"
78
+
79
+ allocate_aliases
80
+ @table = Arel::Table.new(
81
+ table_name, :as => aliased_table_name, :engine => arel_engine
82
+ )
83
+ end
84
+
85
+
86
+ def ==(other)
87
+ other.class == self.class &&
88
+ other.reflection == reflection &&
89
+ other.active_record == active_record &&
90
+ other.parent == parent
91
+ end
92
+
93
+ def join_belongs_to_to(relation)
94
+ foreign_key = options[:foreign_key] || reflection.foreign_key
95
+ foreign_type = options[:foreign_type] || reflection.foreign_type
96
+ primary_key = options[:primary_key] || reflection.klass.primary_key
97
+ join_target_table(
98
+ relation,
99
+ target_table[primary_key].eq(parent_table[foreign_key]).
100
+ and(parent_table[foreign_type].eq(active_record.name))
101
+ )
102
+ end
103
+
104
+ end
105
+ end