sprsquish-conditions_fu 0.2.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.
data/TODO.txt ADDED
@@ -0,0 +1,21 @@
1
+ - make 'any' work even when the same attribute_query is specified twice:
2
+ * Person.any(:conditions => { :first_name.eql => "Bob", :first_name.eql => "Nathan" }) (?)
3
+ * Person.any(:conditions => { :first_name.eql => ["Bob", "Nathan"] })
4
+ - make it work with objects that include all types of eager loaded associations (?)
5
+ - include support for joined or included attributes (?)
6
+
7
+ === Notes on Find Hierarchy ===
8
+
9
+ activerecord/base.rb
10
+ find (line 581)
11
+ find_every (line 1484)
12
+ construct_finder_sql (line 1615)
13
+ add_conditions! (line 1729)
14
+ merge_conditions (line 1436)
15
+ sanitize_sql_for_conditions (line 2068)
16
+ sanitize_sql_hash_for_conditions (line 2139)
17
+ attribute_condition (line 1855)
18
+
19
+
20
+ activerecord/associations
21
+ find_with_associations (line 1247)
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 2
4
+ :patch: 0
@@ -0,0 +1,122 @@
1
+ module ConditionsFu
2
+ module Base
3
+ # overriden sanitization in order to highjack the way that the condition string is formed
4
+ # the attribute_condition method needs to have the attribute query object in order to
5
+ # determine which operator to apply for the condition
6
+ # alias_method_chain :sanitize_sql_hash_for_conditions, :attribute_queries
7
+ def sanitize_sql_hash_for_conditions_with_attribute_queries(attrs, table_name = quoted_table_name)
8
+ condition_join_string = attrs.delete(:connect) || ' AND ' # ' OR ' or ' AND '
9
+ attrs = expand_hash_conditions_for_aggregates(attrs)
10
+ # view_statement("[sanitize] initial attrs", attrs)
11
+
12
+ conditions = attrs.map do |attr, value|
13
+ unless value.is_a?(Hash)
14
+ attr_string = attr.to_s
15
+
16
+ # Extract table name from qualified attribute names.
17
+ if attr_string.include?('.')
18
+ table_name, attr_string = attr_string.split('.', 2)
19
+ table_name = connection.quote_table_name(table_name)
20
+ end
21
+
22
+ # Here is the where the difference occurs: attribute_condition_for_query takes in the attr object
23
+ # table_name.`quoted_attribute_name` [result of attribute_condition_for_query]
24
+ attribute_condition_for_query("#{table_name}.#{connection.quote_column_name(attr_string)}", attr, value)
25
+ else
26
+ sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
27
+ end
28
+ end.join(condition_join_string)
29
+
30
+ # view_statement("[sanitize] conditions", conditions)
31
+ # view_statement("[sanitize] attrs values", attrs.values)
32
+ final_result = replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
33
+ # view_statement("[sanitize] final result", final_result)
34
+ end
35
+
36
+ # returns the operator and value portion of the query string for a given condition (i.e "= ?")
37
+ # the actual operator is determined by the attribute_query type ( i.e :name.gt => '>' )
38
+ def attribute_condition_for_query(column, attribute_query, value)
39
+ # immediately return to regularly scheduled programming if the attribute is a plain old symbol
40
+ return "#{column} #{attribute_condition(value)}" unless attribute_query.kind_of?(AttributeCondition)
41
+ return "MATCH(#{column}) AGAINST(? IN BOOLEAN MODE)" if attribute_query.condition_operator == :match
42
+
43
+ # in the case that the attribute is actually a query object, determine the query type
44
+ op = case attribute_query.condition_operator # (i.e :gt, :lt, :like, :eql)
45
+ when :eql then "= ?"
46
+ when :lt then "< ?"
47
+ when :lte then "<= ?"
48
+ when :gt then "> ?"
49
+ when :gte then ">= ?"
50
+ when :like then "LIKE ?"
51
+ when :in then "IN (?)"
52
+ when :not then "NOT IN (?)"
53
+ when :regexp then "REGEXP ?"
54
+ end
55
+ "#{column} #{op}"
56
+ end
57
+
58
+ # similar Model.all which is an alias for Model.find(:all), except concatenates conditions with "OR" instead of "AND"
59
+ # Model.any(:conditions => { :name.like => "%Na%", :age.gt > 30 }) # => `name` LIKE "%Na" OR `age` > 30
60
+ def any(*args)
61
+ options = args.extract_options! # extract the options off the end
62
+ options[:conditions][:connect] = ' OR ' if options[:conditions] # set the condition_join condition
63
+ args << options # add options back on the end
64
+ find(:all, *args)
65
+ end
66
+
67
+ def view_statement(title, expression)
68
+ puts "--------->#{title}<----------"
69
+ puts expression.kind_of?(String) ? "==== #{expression} ======" : "==== #{expression.inspect} ======"
70
+ puts "\n"
71
+ expression
72
+ end
73
+ end
74
+
75
+ # define symbol methods for each possible condition qualifier
76
+ module SymbolQueryExtensions
77
+ [ :eql, :lt, :gt, :gte, :lte, :in, :like, :not, :match, :regexp ].each do |query_operator|
78
+ define_method(query_operator) do
79
+ AttributeCondition.new(self, query_operator)
80
+ end
81
+ end
82
+ end
83
+
84
+ # An attribute condition simply contains an attribute name as well as a condition operator
85
+ class AttributeCondition
86
+ # attr_name : String => the attribute that will be used in the condition (i.e :name, :age, ...)
87
+ # operator : Symbol => the operator which will be used for comparison (i.e :gt, :lt, :like, :eql, ...)
88
+ attr_accessor :attribute_name, :condition_operator
89
+
90
+ def initialize(attribute_name, condition_operator)
91
+ @attribute_name, @condition_operator = attribute_name, condition_operator
92
+ end
93
+
94
+ # returns just the attribute name for simplicity
95
+ def to_s
96
+ @attribute_name.to_s
97
+ end
98
+
99
+ # returns the attribute name as a symbol
100
+ def to_sym
101
+ @attribute_name.to_sym
102
+ end
103
+
104
+ # redefines comparison for attribute condition objects if the attribute name and operator are the same
105
+ def <=>(other)
106
+ [self.condition_operator.to_s, self.attribute_name.to_s] <=> [other.condition_operator.to_s, other.attribute_name.to_s]
107
+ end
108
+
109
+ # redefines equality within the ruby objects based on the new comparison operator
110
+ def ==(other)
111
+ (self <=> other) == 0
112
+ end
113
+
114
+ def eql?(other)
115
+ self.hash == other.hash
116
+ end
117
+
118
+ def hash
119
+ ["AttributeCondition", self.condition_operator.to_s, self.attribute_name.to_s].hash
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,151 @@
1
+ require File.dirname(__FILE__) + "/test_helper.rb"
2
+ require File.dirname(__FILE__) + "/../init.rb"
3
+
4
+ class Person < ActiveRecord::Base
5
+
6
+ end
7
+
8
+ class ConditionsFuTest < Test::Unit::TestCase
9
+ load_all_fixtures
10
+
11
+ def setup
12
+ @nathan = Person.find(1)
13
+ @bob = Person.find(2)
14
+ @joan = Person.find(3)
15
+ @tom = Person.find(4)
16
+ @nathan2 = Person.find(5)
17
+ end
18
+
19
+ # conditions_fu attribute condition tests
20
+
21
+ def test_two_attribute_conditions_with_same_attribute_and_operator_are_equal
22
+ # tests that two attribute conditions that have the same operator are equal
23
+ condition_a = :first_name.eql
24
+ condition_b = :first_name.eql
25
+ assert_equal condition_a, condition_b
26
+ end
27
+
28
+ def test_two_attribute_conditions_with_same_attribute_and_different_operator_are_not_equal
29
+ # tests that two attribute conditions that have a different operator are not equal
30
+ condition_a = :first_name.eql
31
+ condition_b = :first_name.in
32
+ assert_not_equal condition_a, condition_b
33
+ end
34
+
35
+ def test_two_attribute_conditions_with_different_attribute_and_same_operator_are_not_equal
36
+ # tests that two attribute conditions that have a different operator are not equal
37
+ condition_a = :first_name.eql
38
+ condition_b = :last_name.eql
39
+ assert_not_equal condition_a, condition_b
40
+ end
41
+
42
+ def test_two_attribute_conditions_with_same_attribute_and_operator_are_equal_in_hash
43
+ condition_hash_a = { :conditions => { :first_name.eql => "matt" } }
44
+ condition_hash_b = { :conditions => { :first_name.eql => "matt" } }
45
+ assert_equal condition_hash_a, condition_hash_b
46
+ end
47
+
48
+ def test_two_attribute_conditions_with_same_attribute_and_different_operator_are_not_equal_in_hash
49
+ condition_hash_a = { :conditions => { :first_name.eql => "matt" } }
50
+ condition_hash_b = { :conditions => { :first_name.like => "matt" } }
51
+ assert_not_equal condition_hash_a, condition_hash_b
52
+ end
53
+
54
+ # conditions_fu plugin tests
55
+
56
+ def test_equal_operator_should_work
57
+ assert_equal [@bob], Person.all(:conditions => { :first_name.eql => "Bob" })
58
+ assert_equal @bob, Person.first(:conditions => { :first_name.eql => "Bob" })
59
+ assert_equal [@nathan, @tom], Person.all(:conditions => { :occupation.eql => "Student" })
60
+ assert_equal [@bob, @tom], Person.all(:conditions => { :favorite_number.eql => 34 })
61
+ assert_equal [@nathan], Person.all(:conditions => { :first_name.eql => "Nathan", :last_name.eql => "Esquenazi" })
62
+ assert_equal @nathan, Person.first(:conditions => { :first_name.eql => "Nathan", :last_name.eql => "Esquenazi" })
63
+ assert_equal [@nathan, @bob, @nathan2], Person.any(:conditions => { :last_name.eql => "Villa", :first_name.eql => "Nathan" })
64
+ end
65
+
66
+ def test_like_should_work
67
+ assert_equal [@nathan], Person.all(:conditions => { :first_name.like => "Na%", :last_name.like => "%squ%" })
68
+ assert_equal [@nathan, @nathan2], Person.all(:conditions => { :first_name.like => "Nathan" })
69
+ assert_equal @nathan, Person.first(:conditions => { :first_name.like => "Nathan" })
70
+ assert_equal [@nathan, @nathan2], Person.all(:conditions => { :first_name.like => "Nat%" })
71
+ assert_equal [@nathan, @bob, @joan, @tom], Person.all(:conditions => { :occupation.like => "%t%" })
72
+ assert_equal [], Person.all(:conditions => { :first_name.like => "Nat" })
73
+ assert_equal [@nathan, @bob, @nathan2], Person.any(:conditions => { :last_name.like => "%Vil%", :first_name.like => "%Nat%" })
74
+ end
75
+
76
+ def test_less_than_should_work
77
+ assert_equal [@nathan], Person.all(:conditions => { :age.lt => 23 })
78
+ assert_equal [@nathan, @tom], Person.all(:conditions => { :age.lt => 30 })
79
+ assert_equal [@joan], Person.all(:conditions => { :favorite_number.lt => 25 })
80
+ assert_equal @joan, Person.first(:conditions => { :favorite_number.lt => 25 })
81
+ assert_equal [@nathan, @joan], Person.any(:conditions => { :age.lt => 22, :age.gte => 56 })
82
+ end
83
+
84
+ def test_less_equal_than_should_work
85
+ assert_equal [@nathan], Person.all(:conditions => { :age.lte => 23 })
86
+ assert_equal [@nathan, @tom], Person.all(:conditions => { :age.lte => 30 })
87
+ assert_equal [@joan], Person.all(:conditions => { :favorite_number.lte => 25 })
88
+ assert_equal @joan, Person.first(:conditions => { :favorite_number.lte => 25 })
89
+ assert_equal [@nathan], Person.all(:conditions => { :age.lte => 21, :first_name => "Nathan" })
90
+ assert_equal [@nathan, @joan], Person.any(:conditions => { :age.lte => 21, :age.gte => 56 })
91
+ end
92
+
93
+ def test_greater_than_should_work
94
+ assert_equal [@joan, @nathan2], Person.all(:conditions => { :age.gt => 50 })
95
+ assert_equal [@bob], Person.all(:conditions => { :age.gt => 30, :age.lt => 40 })
96
+ assert_equal @bob, Person.first(:conditions => { :age.gt => 30, :age.lt => 40 })
97
+ assert_equal [@nathan2], Person.all(:conditions => { :favorite_number.gt => 500})
98
+ end
99
+
100
+ def test_greater_equal_than_should_work
101
+ assert_equal [@joan, @nathan2], Person.all(:conditions => { :age.gte => 50 })
102
+ assert_equal [@bob], Person.all(:conditions => { :age.gte => 30, :age.lt => 40 })
103
+ assert_equal [@nathan2], Person.all(:conditions => { :favorite_number.gte => 500})
104
+ assert_equal [@joan, @nathan2], Person.all(:conditions => { :age.gte => 54 })
105
+ assert_equal [@nathan], Person.all(:conditions => { :age.gte => 21, :age.lte => 21 })
106
+ end
107
+
108
+ def test_in_should_work
109
+ assert_equal [@nathan, @joan, @nathan2], Person.all(:conditions => { :first_name.in => ["Nathan", "Joan"] })
110
+ assert_equal [@bob, @joan, @tom], Person.all(:conditions => { :favorite_number.in => [34, 23] })
111
+ assert_equal [@nathan, @tom], Person.all(:conditions => { :occupation.in => ["Student"] })
112
+ assert_equal @nathan, Person.first(:conditions => { :occupation.in => ["Student"] })
113
+ assert_equal [@nathan, @bob, @tom], Person.any(:conditions => { :occupation.in => ["Student"], :age.in => [38] })
114
+ end
115
+
116
+ def test_not_in_should_work
117
+ assert_equal [@nathan, @nathan2], Person.all(:conditions => { :first_name.not => ["Tom", "Joan", "Bob"] })
118
+ assert_equal [@bob, @tom], Person.all(:conditions => { :age.not => [21, 56, 54] })
119
+ assert_equal @bob, Person.first(:conditions => { :age.not => [21, 56, 54] })
120
+ assert_equal [@bob, @joan, @tom, @nathan2], Person.any(:conditions => { :age.not => [21, 56, 54], :age.not => [21] })
121
+ end
122
+
123
+ # regression tests (make sure the plugin doesn't destroy the existing find options)
124
+
125
+ def test_simple_expectations
126
+ # Sanity Check
127
+ assert_equal 5, Person.all.size
128
+ assert_equal "Esquenazi", Person.find(1).last_name
129
+ assert_equal "Tom", Person.find(4).first_name
130
+ end
131
+
132
+ def test_old_string_queries_work
133
+ # test that the old activerecord queries still work
134
+ assert_equal [@nathan, @nathan2], Person.all(:conditions => { :first_name => "Nathan" })
135
+ assert_equal @nathan, Person.first(:conditions => { :first_name => "Nathan" })
136
+ assert_equal [@nathan, @nathan2], Person.find(:all, :conditions => { :first_name => "Nathan" })
137
+ assert_equal [@nathan], Person.find(:all, :conditions => { :first_name => "Nathan", :last_name => "Esquenazi" })
138
+ assert_equal @nathan, Person.find(:first, :conditions => { :first_name => "Nathan", :last_name => "Esquenazi" })
139
+ assert_equal [@bob], Person.find(:all, :conditions => { :occupation => "Contractor", :first_name => "Bob" })
140
+ end
141
+
142
+ def test_old_range_queries_work
143
+ assert_equal [@bob], Person.all(:conditions => { :age => 37..39 })
144
+ assert_equal [@bob, @tom], Person.all(:conditions => { :favorite_number => 33..35 })
145
+ end
146
+
147
+ def test_old_array_queries_work
148
+ assert_equal [@nathan, @bob, @tom, @nathan2], Person.all(:conditions => { :first_name => ['Nathan', 'Bob', 'Tom'] })
149
+ assert_equal [@bob, @tom], Person.all(:conditions => { :age => [24, 38] })
150
+ end
151
+ end
data/test/database.yml ADDED
@@ -0,0 +1,18 @@
1
+ sqlite3_file:
2
+ adapter: sqlite3
3
+ dbfile: conditions_fu.sqlite.db
4
+ sqlite3:
5
+ adapter: sqlite3
6
+ database: ":memory:"
7
+ postgresql:
8
+ adapter: postgresql
9
+ username: postgres
10
+ password: postgres
11
+ database: conditions_fu_test
12
+ min_messages: ERROR
13
+ mysql:
14
+ adapter: mysql
15
+ host: localhost
16
+ username: conditions_fu
17
+ password: conditions_fu
18
+ database: conditions_fu_test
@@ -0,0 +1,61 @@
1
+ # == Schema Information
2
+ # Schema version: 20080605094825
3
+ #
4
+ # Table name: people
5
+ #
6
+ # id :integer not null, primary key
7
+ # first_name :string(255)
8
+ # last_name :string(255)
9
+ # occupation :string(255)
10
+ # age :integer
11
+ # phone :integer
12
+ # favorite_number :integer
13
+ # created_at :datetime
14
+ # updated_at :datetime
15
+ #
16
+
17
+ nathan:
18
+ id: 1
19
+ first_name: Nathan
20
+ last_name: "Esquenazi"
21
+ occupation: "Student"
22
+ age: 21
23
+ phone: 6267685768
24
+ favorite_number: 121
25
+
26
+ bob:
27
+ id: 2
28
+ first_name: "Bob"
29
+ last_name: "Villa"
30
+ occupation: "Contractor"
31
+ age: 38
32
+ phone: 123456787
33
+ favorite_number: 34
34
+
35
+ joan:
36
+ id: 3
37
+ first_name: "Joan"
38
+ last_name: "Taylor"
39
+ occupation: "Doctor"
40
+ age: 56
41
+ phone: 876876895
42
+ favorite_number: 23
43
+
44
+ tom:
45
+ id: 4
46
+ first_name: "Tom"
47
+ last_name: "Fields"
48
+ occupation: "Student"
49
+ age: 24
50
+ phone: 2134324634
51
+ favorite_number: 34
52
+
53
+ nathan2:
54
+ id: 5
55
+ first_name: "Nathan"
56
+ last_name: "Goldberg"
57
+ occupation: "Lawyer"
58
+ age: 54
59
+ phone: 2453545435
60
+ favorite_number: 1000
61
+
data/test/schema.rb ADDED
@@ -0,0 +1,8 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+
3
+ create_table :people do |t|
4
+ t.string :first_name, :last_name, :occupation
5
+ t.integer :age, :phone, :favorite_number
6
+ end
7
+
8
+ end
@@ -0,0 +1,39 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'active_record'
4
+ require 'active_record/fixtures'
5
+
6
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
7
+ RAILS_DEFAULT_LOGGER = ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
8
+ ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
9
+
10
+ load(File.dirname(__FILE__) + "/schema.rb") if File.exist?(File.dirname(__FILE__) + "/schema.rb")
11
+
12
+ Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
13
+ $LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
14
+
15
+ class Test::Unit::TestCase #:nodoc:
16
+ def create_fixtures(*table_names)
17
+ if block_given?
18
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
19
+ else
20
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
21
+ end
22
+ end
23
+
24
+ def self.load_all_fixtures
25
+ all_fixtures = Dir.glob("#{File.dirname(__FILE__)}/fixtures/*.yml").collect do |f|
26
+ puts "Loading fixture '#{f}'"
27
+ File.basename(f).gsub(/\.yml$/, "").to_sym
28
+ end
29
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, all_fixtures)
30
+ end
31
+
32
+ # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
33
+ self.use_transactional_fixtures = true
34
+
35
+ # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
36
+ self.use_instantiated_fixtures = false
37
+
38
+ # Add more helper methods to be used by all tests here...
39
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sprsquish-conditions_fu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Nathan Esquenazi
8
+ - Jeff Smick
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-01-29 00:00:00 -08:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: README
18
+ email: sprsquish@gmail.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - TODO.txt
27
+ - VERSION.yml
28
+ - lib/conditions_fu.rb
29
+ - test/conditions_fu_test.rb
30
+ - test/database.yml
31
+ - test/fixtures
32
+ - test/fixtures/people.yml
33
+ - test/schema.rb
34
+ - test/test_helper.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/sprsquish/conditions_fu
37
+ post_install_message:
38
+ rdoc_options:
39
+ - --inline-source
40
+ - --charset=UTF-8
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.2.0
59
+ signing_key:
60
+ specification_version: 2
61
+ summary: README
62
+ test_files: []
63
+