inequal_opportunity 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.swp
2
+ *.swo
3
+ database.yml
4
+ pkg/
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ryan Angilly
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 ADDED
@@ -0,0 +1,68 @@
1
+ = Inequal Opportunity
2
+
3
+ inequal_opportunity exists because this does not work in ActiveRecord:
4
+
5
+ Doctor.all(:joins => :patients, :conditions => {:patients => ['age > ?', 20]})
6
+
7
+ You will get an unknown column exception looking for `doctors`.`age`.
8
+ Instead, you need to write:
9
+
10
+ Doctor.all(:joins => :pateints, :conditions => ['`patients`.`age` > ?', 20])
11
+
12
+ Putting the string table name in the query annoyed me. On top of that,
13
+ I always wanted a way to eliminate strings from my named scopes and queries.
14
+ So now with inequal_opportunity you can write:
15
+
16
+ Doctor.all(:joins => :patients, :conditions => {:patients => {:age => gt(20)}})
17
+
18
+ Not only is it prettier (hashed), but ActiveRecord will keep track of
19
+ table names for you.
20
+
21
+ ActiveRecord looks for Array and Range types to decide whether to use
22
+ 'IN' or 'BETWEEN' instead of the normal '=' as the comparison operator
23
+ when generating SQL. inequal_opportunity extends that pattern by
24
+ wrapping the value in a series of ActiveRecord::Inequality::Base classes.
25
+ Just wrap the value with one of the following helper functions:
26
+
27
+ gte() => >=
28
+ gt() => >
29
+ lte() => <=
30
+ le() => <
31
+ ne() => <>
32
+ ne(nil) => IS NOT
33
+
34
+ and the appropriate SQL will be generated. This works in finds, as shown
35
+ above, in counts:
36
+
37
+ People.count(:age => gt(20))
38
+
39
+ in named scopes:
40
+
41
+ class People < AR::B
42
+ named_scope :underage, :conditions => {:age => lte(18)}
43
+ end
44
+
45
+ in default scopes:
46
+
47
+ class Feedback < AR::B
48
+ default_scope :conditions => {:type => ne('spam')}
49
+ end
50
+
51
+ and pretty much everywhere else I've tested manually.
52
+
53
+ Test coverage is kind of sparse right now, and it's only been tested
54
+ on MySQL. But it has been rock solid in every situation I've thrown it in,
55
+ so I figured the best way to improve it was to release the hounds (YOU).
56
+
57
+ Note that I am not completely satisfied with the way I alias
58
+ ActiveRecord::Base.expand_range_bind_variables. It smells, but it works.
59
+ Suggestions welcome.
60
+
61
+ == License
62
+
63
+ inequal_opportunity is released under the MIT license.
64
+
65
+
66
+ == Support
67
+
68
+ Just email me at ryan@angilly.com with questions, bugs, or patches.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "inequal_opportunity"
5
+ gemspec.summary = "Adds mergable, stackable inequality statements to ActiveRecord conditions"
6
+ gemspec.email = "ryan@angilly.com"
7
+ gemspec.homepage = "http://github.com/ryana/inequal_opportunity"
8
+ gemspec.description = "Adds mergable, stackable inequality statements to ActiveRecord conditions"
9
+ gemspec.authors = ["Ryan Angilly"]
10
+ end
11
+ Jeweler::GemcutterTasks.new
12
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 2
4
+ :patch: 0
@@ -0,0 +1,53 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{inequal_opportunity}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ryan Angilly"]
12
+ s.date = %q{2010-05-18}
13
+ s.description = %q{Adds mergable, stackable inequality statements to ActiveRecord conditions}
14
+ s.email = %q{ryan@angilly.com}
15
+ s.extra_rdoc_files = [
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "MIT-LICENSE",
21
+ "README",
22
+ "Rakefile",
23
+ "VERSION.yml",
24
+ "inequal_opportunity.gemspec",
25
+ "init.rb",
26
+ "lib/inequal_opportunity.rb",
27
+ "test/database.example.yml",
28
+ "test/db_setup.rb",
29
+ "test/inequal_opportunity_test.rb",
30
+ "test/test_helper.rb"
31
+ ]
32
+ s.homepage = %q{http://github.com/ryana/inequal_opportunity}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = %q{1.3.6}
36
+ s.summary = %q{Adds mergable, stackable inequality statements to ActiveRecord conditions}
37
+ s.test_files = [
38
+ "test/db_setup.rb",
39
+ "test/inequal_opportunity_test.rb",
40
+ "test/test_helper.rb"
41
+ ]
42
+
43
+ if s.respond_to? :specification_version then
44
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
45
+ s.specification_version = 3
46
+
47
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
48
+ else
49
+ end
50
+ else
51
+ end
52
+ end
53
+
data/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ $: << File.dirname(__FILE__) + "/lib/"
2
+
3
+ require 'active_record'
4
+ require 'inequal_opportunity'
@@ -0,0 +1,154 @@
1
+ module ActiveRecord
2
+ module Inequality
3
+
4
+ class InequalError < Exception
5
+ end
6
+
7
+ class Base
8
+ attr_accessor :value
9
+
10
+ def initialize(val)
11
+ self.value = val
12
+ end
13
+
14
+ def operator
15
+ raise('Nope')
16
+ end
17
+
18
+ def ==(val)
19
+ self.operator == val.operator && self.value == val.value
20
+ end
21
+
22
+ def string_value
23
+ value ? "'#{value}'" : 'nil'
24
+ end
25
+
26
+ def inspect
27
+ " #{operator} #{self.string_value}"
28
+ end
29
+
30
+ end
31
+
32
+ class GreaterThanEqual < Base
33
+ def operator
34
+ '>='
35
+ end
36
+ end
37
+
38
+ class GreaterThan < Base
39
+ def operator
40
+ '>'
41
+ end
42
+ end
43
+
44
+ class LessThanEqual < Base
45
+ def operator
46
+ '<='
47
+ end
48
+ end
49
+
50
+ class LessThan < Base
51
+ def operator
52
+ '<'
53
+ end
54
+ end
55
+
56
+ class NotEqual < Base
57
+ def operator
58
+ case value
59
+ when NilClass, TrueClass, FalseClass
60
+ 'IS NOT'
61
+ when Array
62
+ 'NOT IN'
63
+ else
64
+ '<>'
65
+ end
66
+ end
67
+ end
68
+
69
+ class Like < Base
70
+ def operator
71
+ 'LIKE'
72
+ end
73
+
74
+ # This method is why I love Ruby
75
+ def value(override = false)
76
+ v = super(*[])
77
+
78
+ if !override && !v.is_a?(Numeric) && !v.is_a?(String)
79
+ raise InequalError, "Passing #{v.class} to Like. You can't possibly want to do this"
80
+ end
81
+
82
+ "%#{v}%"
83
+ end
84
+ end
85
+
86
+ module WrapperMethods
87
+
88
+ def lte(val)
89
+ ActiveRecord::Inequality::LessThanEqual.new(val)
90
+ end
91
+
92
+ def lt(val)
93
+ ActiveRecord::Inequality::LessThan.new(val)
94
+ end
95
+
96
+ def gte(val)
97
+ ActiveRecord::Inequality::GreaterThanEqual.new(val)
98
+ end
99
+
100
+ def gt(val)
101
+ ActiveRecord::Inequality::GreaterThan.new(val)
102
+ end
103
+
104
+ def ne(val)
105
+ ActiveRecord::Inequality::NotEqual.new(val)
106
+ end
107
+
108
+ def like(val)
109
+ ActiveRecord::Inequality::Like.new(val)
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+
116
+ class Base
117
+ class << self
118
+ alias attribute_condition_orig attribute_condition
119
+ def attribute_condition(quoted_column_name, argument)
120
+ if argument.is_a? ActiveRecord::Inequality::Base
121
+ question = argument.value.is_a?(Array) ? '(?)' : '?'
122
+ "#{quoted_column_name} #{argument.operator} #{question}"
123
+ else
124
+ attribute_condition_orig(quoted_column_name, argument)
125
+ end
126
+ end
127
+
128
+ alias expand_range_bind_variables_orig expand_range_bind_variables
129
+ def expand_range_bind_variables(bind_vars)
130
+ expanded = []
131
+
132
+ bind_vars.each do |var|
133
+ next if var.is_a?(Hash)
134
+
135
+ if var.is_a?(Range)
136
+ expanded << var.first
137
+ expanded << var.last
138
+ elsif var.is_a?(ActiveRecord::Inequality::Base)
139
+ expanded << var.value
140
+ else
141
+ expanded << var
142
+ end
143
+ end
144
+
145
+ expanded
146
+ end
147
+
148
+ end
149
+ end
150
+
151
+ end
152
+
153
+ include ActiveRecord::Inequality::WrapperMethods
154
+
@@ -0,0 +1,8 @@
1
+ source:
2
+ adapter: mysql
3
+ database:
4
+ host: localhost
5
+ port: 3306
6
+ username:
7
+ password:
8
+
data/test/db_setup.rb ADDED
@@ -0,0 +1,13 @@
1
+ DB = YAML::load(File.open(File.join(File.dirname(__FILE__), 'database.yml'))).symbolize_keys!
2
+ ActiveRecord::Base.establish_connection(DB[:source])
3
+
4
+ TABLES = %w(mains seconds)
5
+
6
+ TABLES.each do |t|
7
+ ActiveRecord::Base.connection.execute("drop table if exists #{t};")
8
+ end
9
+
10
+ TABLES.each do |t|
11
+ ActiveRecord::Base.connection.execute("create table #{t} (id integer, val integer, seconds_id integer, mains_id integer, created_at datetime default null);")
12
+ end
13
+
@@ -0,0 +1,175 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+ require File.join(File.dirname(__FILE__), 'db_setup')
3
+
4
+ METHOD_SYMBOLS = [:gt, :gte, :lt, :lte, :ne, :like]
5
+
6
+ class Main < ActiveRecord::Base
7
+ belongs_to :seconds
8
+ named_scope :newer_than, lambda {|time| {:conditions => {:created_at => gte(time) }} }
9
+
10
+ METHOD_SYMBOLS.each do |s|
11
+ named_scope :"try_#{s}", lambda {|i| {:conditions => {:id => send(s, i)}} }
12
+ end
13
+
14
+ end
15
+
16
+ class Second < ActiveRecord::Base
17
+ has_many :mains
18
+ end
19
+
20
+ class InequalOpportunityTest < Test::Unit::TestCase
21
+
22
+ def setup
23
+ TABLES.each do |t|
24
+ ActiveRecord::Base.connection.execute("DELETE FROM #{t};")
25
+ end
26
+ end
27
+
28
+ context "a model" do
29
+ setup do
30
+ @model = Main
31
+ end
32
+
33
+ should "respond to gte" do
34
+ assert @model.respond_to?(:gte)
35
+ end
36
+
37
+ should "should work with a named_scope" do
38
+ assert_equal Main.newer_than(2.days.ago).all, []
39
+ end
40
+
41
+ should "generate proper sql for array" do
42
+ METHOD_SYMBOLS.each do |s|
43
+
44
+ if s == :ne
45
+ assert_equal Main.try_ne([1,2,3]).first, nil
46
+ elsif s == :like
47
+ assert_raises ActiveRecord::Inequality::InequalError do
48
+ assert_equal Main.try_like([1,2,3]).first, nil
49
+ end
50
+ else
51
+ assert_raises ActiveRecord::StatementInvalid do
52
+ assert_equal Main.send(:"try_#{s}", [1,2,3]).first, nil
53
+ end
54
+ end
55
+ end
56
+
57
+ assert_equal Main.try_ne([1,2,3]).first, nil
58
+ assert_equal Main.try_ne([1,2,3]).count, 0
59
+ end
60
+
61
+ should "generate proper sql for nil and not nil" do
62
+ METHOD_SYMBOLS.each do |s|
63
+ assert_equal Main.send(:"try_#{s}", 1).first, nil
64
+ assert_equal Main.send(:"try_#{s}", nil).first, nil unless s == :like
65
+ end
66
+
67
+ assert_raises ActiveRecord::Inequality::InequalError do
68
+ Main.try_like(nil).first
69
+ end
70
+ end
71
+
72
+ should "properly scope based on gte" do
73
+ time = 2.days.ago
74
+ num = 3
75
+ num.times { Main.create(:val => 3) }
76
+ num.times { Main.create(:val => 1) }
77
+
78
+ assert_equal Main.count(:conditions => {:val => gte(2)}), num
79
+ end
80
+
81
+ end
82
+
83
+ context "an instance" do
84
+ setup do
85
+ @main = Main.new
86
+ end
87
+
88
+ should "respond to gte" do
89
+ assert @main.respond_to?(:gte)
90
+ end
91
+
92
+ end
93
+
94
+ context "a parent" do
95
+ setup do
96
+ @second = Second.new
97
+ end
98
+
99
+ should "see through assocaitions" do
100
+ assert @second.mains.respond_to?(:gte)
101
+ end
102
+ end
103
+
104
+ context "Object" do
105
+ should "have gte" do
106
+ wrapped = gte(5)
107
+ assert_equal wrapped.operator, '>='
108
+ assert_equal wrapped, ActiveRecord::Inequality::GreaterThanEqual.new(5)
109
+ end
110
+
111
+ should "have gt" do
112
+ wrapped = gt(5)
113
+ assert_equal wrapped.operator, '>'
114
+ assert_equal wrapped, ActiveRecord::Inequality::GreaterThan.new(5)
115
+ end
116
+
117
+ should "have lte" do
118
+ wrapped = lte(5)
119
+ assert_equal wrapped.operator, '<='
120
+ assert_equal wrapped, ActiveRecord::Inequality::LessThanEqual.new(5)
121
+ end
122
+
123
+ should "have lt" do
124
+ wrapped = lt(5)
125
+ assert_equal wrapped.operator, '<'
126
+ assert_equal wrapped, ActiveRecord::Inequality::LessThan.new(5)
127
+ end
128
+
129
+ should "have ne" do
130
+ wrapped = ne(5)
131
+ assert_equal wrapped.operator, '<>'
132
+ assert_equal wrapped, ActiveRecord::Inequality::NotEqual.new(5)
133
+ end
134
+
135
+ should "have a different operator when calling ne w/ nil" do
136
+ wrapped = ne(nil)
137
+ assert_equal wrapped.operator, 'IS NOT'
138
+ assert_equal wrapped, ActiveRecord::Inequality::NotEqual.new(nil)
139
+ end
140
+
141
+ should "have a different operator when calling ne w/ true" do
142
+ wrapped = ne(true)
143
+ assert_equal wrapped.operator, 'IS NOT'
144
+ assert_equal wrapped, ActiveRecord::Inequality::NotEqual.new(true)
145
+ end
146
+
147
+ should "have a different operator when calling ne w/ false" do
148
+ wrapped = ne(false)
149
+ assert_equal wrapped.operator, 'IS NOT'
150
+ assert_equal wrapped, ActiveRecord::Inequality::NotEqual.new(false)
151
+ end
152
+
153
+
154
+
155
+ should "have like" do
156
+ wrapped = like('ryan')
157
+ assert_equal wrapped.operator, 'LIKE'
158
+ assert_equal wrapped, ActiveRecord::Inequality::Like.new('ryan')
159
+ end
160
+
161
+ end
162
+
163
+ context "expand_range_bind_variables_orig" do
164
+ should "return a proper array" do
165
+ range_start = 1
166
+ range_end = 9
167
+
168
+ input = [{:a => 1}, (range_start..range_end), "Yo", {:c => 'd'}, 23, [1,2,3]]
169
+ res = ActiveRecord::Base.send :expand_range_bind_variables_orig, input
170
+
171
+ assert_equal res, [range_start, range_end, "Yo", 23, [1,2,3]]
172
+ end
173
+ end
174
+
175
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require File.dirname(__FILE__) + '/../init'
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inequal_opportunity
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
+ platform: ruby
11
+ authors:
12
+ - Ryan Angilly
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-18 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Adds mergable, stackable inequality statements to ActiveRecord conditions
22
+ email: ryan@angilly.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README
29
+ files:
30
+ - .gitignore
31
+ - MIT-LICENSE
32
+ - README
33
+ - Rakefile
34
+ - VERSION.yml
35
+ - inequal_opportunity.gemspec
36
+ - init.rb
37
+ - lib/inequal_opportunity.rb
38
+ - test/database.example.yml
39
+ - test/db_setup.rb
40
+ - test/inequal_opportunity_test.rb
41
+ - test/test_helper.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/ryana/inequal_opportunity
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --charset=UTF-8
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.6
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Adds mergable, stackable inequality statements to ActiveRecord conditions
72
+ test_files:
73
+ - test/db_setup.rb
74
+ - test/inequal_opportunity_test.rb
75
+ - test/test_helper.rb