where_lower 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f84a4041a532127bb4bb9117fc11ecda4992f7ff
4
- data.tar.gz: 51596a49ea5ee3f71eb6e375bc26562d90a4bf8a
3
+ metadata.gz: 563ea05e5d3855494dd93009c36748bd8d14356b
4
+ data.tar.gz: fe1e5b16a0a68258161ef14c4bd5c7553180ff3e
5
5
  SHA512:
6
- metadata.gz: 9bdd4d235dfa5ff8e92b2e59877c6a1cafd7f29d1fa50d6dced92e72aceab149a0f55b5d24058992e4adf38ff65f98bd9ea6289d783d391ad3dacb3faee4fa3e
7
- data.tar.gz: 077019dc9f7008c13fab4cc2975e259fbcb2d17eb5f3d56dfcc744e0bf065b4705c38d6c333d91c8ba505b2b8038001a0b43515c3207f3577a816730a110279a
6
+ metadata.gz: 9b9d6ff276b6223929801b1d29da190c1b71ecf02dcaf68d6cf5abeceb66de2cbae3c1c80898b17cb66223962a17b89ceb11a0a03bdde105e4ab560d15d13d8d
7
+ data.tar.gz: e2a2b9c8b334f8ce163e7fb23f44278e33d6e058182760ff596714f75f93f49ae840770169ba562a02a1e3e0683b609c4e7d3679fdae286218e54f3f3257d939
data/.travis.yml CHANGED
@@ -1,8 +1,9 @@
1
- script: "bundle exec rspec"
1
+ before_install: "gem install bundler -v=1.5.2"
2
2
  rvm:
3
3
  - 1.9.2
4
4
  - 1.9.3
5
5
  - 2.0.0
6
+ - 2.1.0
6
7
  gemfile:
7
8
  - gemfiles/rails3_1.gemfile
8
9
  - gemfiles/rails3_2.gemfile
data/Appraisals CHANGED
@@ -5,11 +5,11 @@ appraise "rails3-1" do
5
5
  end
6
6
 
7
7
  appraise "rails3-2" do
8
- version = "3.2.14"
8
+ version = "3.2.16"
9
9
  gem 'activerecord', version
10
10
  end
11
11
 
12
12
  appraise "rails4-0" do
13
- version = "4.0.0"
13
+ version = "4.0.2"
14
14
  gem 'activerecord', version
15
15
  end
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ ### Changelog
2
+
3
+
4
+ - **0.3.0**
5
+ - Add: You can now pass a nested hash for associaiton conditions (see README)
6
+ - Change: Now it requires at least ruby 1.9.2
7
+
8
+ - **0.2.0**
9
+ - Fix: You can now use this with named scopes
10
+ - Change: Use dynamic query string instead of Arel Table to generate relation (which cause the bug)
11
+ - Dev: Add a few test for compatibility with gem `squeel`
12
+
13
+ - **0.1.0**
14
+ - Initail Release
data/Gemfile CHANGED
@@ -1,11 +1,3 @@
1
1
  source 'https://rubygems.org'
2
- gemspec
3
2
 
4
- gem 'activerecord'
5
- gem 'squeel'
6
- gem 'appraisal'
7
- gem 'rake'
8
- gem 'sqlite3'
9
- gem 'rspec'
10
- gem 'database_cleaner'
11
- gem 'coveralls', require: false
3
+ gemspec
data/README.md CHANGED
@@ -32,6 +32,18 @@ Other types will not be touched
32
32
  SomeActiveRecordClass.where_lower(attribute1: 'AbC', attribute2: ['stRing', 123, :symBol], attribute3: ('AA'..'AZ'))
33
33
  ```
34
34
 
35
+ Since `0.3.0`
36
+ You can pass a nested hash (1 level deep only) for association condition
37
+ ```ruby
38
+ record.association_records.where_lower(association_table: {association_column: value})
39
+ ```
40
+
41
+ You can also add table name in key if you are using it with association
42
+ I don't plan to support any "smart" table guessing though
43
+ ```ruby
44
+ record.association_records.where_lower('association_table.association_column' => value)
45
+ ```
46
+
35
47
  Contributors
36
48
  ============
37
49
  [Matthew Rudy Jacobs](https://github.com/matthewrudy) (Who wrote the first version of `where_lower` method)
data/Rakefile CHANGED
@@ -1,11 +1,15 @@
1
- require 'appraisal'
2
- require 'rubygems'
3
- require 'bundler/setup'
4
- require 'bundler/gem_tasks'
5
- require 'rspec/core/rake_task'
1
+ require "appraisal"
2
+ require "bundler"
3
+ require "rspec/core/rake_task"
6
4
 
7
- task :default do
8
- sh "rake appraisal:install && rake appraisal spec"
9
- end
5
+ Bundler::GemHelper.install_tasks
10
6
 
11
7
  RSpec::Core::RakeTask.new(:spec)
8
+
9
+ if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
10
+ task :default do
11
+ sh "rake appraisal:install && rake appraisal spec"
12
+ end
13
+ else
14
+ task :default => :spec
15
+ end
@@ -2,13 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "squeel"
6
- gem "appraisal"
7
- gem "rake"
8
- gem "sqlite3"
9
- gem "rspec"
10
- gem "database_cleaner"
11
- gem "coveralls", :require=>false
12
5
  gem "activerecord", "3.1.12"
13
6
 
14
7
  gemspec :path=>"../"
@@ -2,13 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "squeel"
6
- gem "appraisal"
7
- gem "rake"
8
- gem "sqlite3"
9
- gem "rspec"
10
- gem "database_cleaner"
11
- gem "coveralls", :require=>false
12
- gem "activerecord", "3.2.14"
5
+ gem "activerecord", "3.2.16"
13
6
 
14
7
  gemspec :path=>"../"
@@ -2,13 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "squeel"
6
- gem "appraisal"
7
- gem "rake"
8
- gem "sqlite3"
9
- gem "rspec"
10
- gem "database_cleaner"
11
- gem "coveralls", :require=>false
12
- gem "activerecord", "4.0.0"
5
+ gem "activerecord", "4.0.2"
13
6
 
14
7
  gemspec :path=>"../"
@@ -0,0 +1,19 @@
1
+ module WhereLower
2
+ module ActiveRecordExtension
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def where_lower(fields)
9
+ fields.is_a?(Hash) or raise AugumentError, 'fields is not a Hash'
10
+
11
+ WhereLower::Base.spawn_lower_scope(self, fields)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ ActiveRecord::Base.class_eval do
18
+ include ::WhereLower::ActiveRecordExtension
19
+ end
@@ -0,0 +1,29 @@
1
+ module WhereLower
2
+ SEPERATOR = '.'.freeze
3
+
4
+ class TooDeepNestedConditions < ArgumentError; end
5
+
6
+ module Base
7
+ class << self
8
+ # @params [ActiveRecord::Relation, ActiveRecord::Base] scope
9
+ # @params [Hash] fields
10
+ def spawn_lower_scope(scope, fields)
11
+ fields.inject(scope) do |new_scope, (name, value)|
12
+ spawn_lower_scope_by_type(new_scope, name, value)
13
+ end
14
+ end
15
+
16
+ # @params [ActiveRecord::Relation, ActiveRecord::Base] scope
17
+ # @params [String, Symbol] column_or_table_name When used with nested hash, this is table name
18
+ # @params [Object] value
19
+ # @params [String, Symbol] prefix used when doing recursive call
20
+ #
21
+ # @raise [TooDeepNestedConditions] if the conditions hash has is too deep nested
22
+ #
23
+ # @return [ActiveRecord::Relation]
24
+ def spawn_lower_scope_by_type(scope, column_or_table_name, value)
25
+ ScopeSpawner.spawn(scope, column_or_table_name, value)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,107 @@
1
+ module WhereLower
2
+ class ScopeSpawner
3
+ attr_reader :scope, :column_or_table_name, :value, :prefix
4
+
5
+ def self.spawn(*args)
6
+ new(*args).spawn
7
+ end
8
+
9
+ def initialize(scope, column_or_table_name, value, prefix = nil)
10
+ @scope, @column_or_table_name, @value, @prefix = scope, column_or_table_name, value, prefix
11
+ end
12
+
13
+ def scope_arguments
14
+ [scope, column_or_table_name, value, prefix]
15
+ end
16
+
17
+ # acts as factory
18
+ def spawn
19
+ case value
20
+ when Hash
21
+ HashScopeSpawner.spawn(*scope_arguments)
22
+ when String
23
+ StringScopeSpawner.spawn(*scope_arguments)
24
+ when Range
25
+ RangeScopeSpawner.spawn(*scope_arguments)
26
+ when Array
27
+ ArrayScopeSpawner.spawn(*scope_arguments)
28
+ else
29
+ BasicScopeSpawner.spawn(*scope_arguments)
30
+ end
31
+ end
32
+
33
+
34
+ private
35
+
36
+ class BasicScopeSpawner < ScopeSpawner
37
+ def spawn
38
+ scope.where(column_name => value)
39
+ end
40
+
41
+ private
42
+
43
+ def column_name
44
+ [prefix, column_or_table_name].compact.join(SEPERATOR)
45
+ end
46
+ end
47
+
48
+ # @abstract
49
+ class EqualScopeSpawner < BasicScopeSpawner
50
+ def spawn
51
+ scope.where(query_string, processed_value)
52
+ end
53
+
54
+ private
55
+
56
+ def query_string
57
+ "#{column_name} = ?"
58
+ end
59
+
60
+ def processed_value
61
+ value
62
+ end
63
+ end
64
+
65
+ class StringScopeSpawner < EqualScopeSpawner
66
+ def query_string
67
+ "lower(#{column_name}) = ?"
68
+ end
69
+
70
+ def processed_value
71
+ value.downcase
72
+ end
73
+ end
74
+
75
+ class RangeScopeSpawner < EqualScopeSpawner
76
+ def query_string
77
+ "lower(#{column_name}) IN (?)"
78
+ end
79
+
80
+ def processed_value
81
+ Range.new(value.begin.to_s.downcase, value.end.to_s.downcase, value.exclude_end?)
82
+ end
83
+ end
84
+
85
+ class ArrayScopeSpawner < EqualScopeSpawner
86
+ def query_string
87
+ "lower(#{column_name}) IN (?)"
88
+ end
89
+
90
+ def processed_value
91
+ value.to_a.map {|x| x.to_s.downcase}
92
+ end
93
+ end
94
+
95
+ class HashScopeSpawner < BasicScopeSpawner
96
+ def spawn
97
+ # If prefix already exists, that means we are in association table already, which cannot accept another hash
98
+ # This gem has no ability to handle deep nested associaiton reflection yet
99
+ raise TooDeepNestedConditions unless prefix.nil?
100
+
101
+ value.inject(scope) do |new_scope, (column_name, column_value)|
102
+ ScopeSpawner.spawn(new_scope, column_name, column_value, column_or_table_name)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -1,3 +1,3 @@
1
1
  module WhereLower
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/where_lower.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'active_record'
2
2
  require 'where_lower/version'
3
- require 'where_lower/core'
4
-
5
- ActiveRecord::Base.send(:include, WhereLower::Core)
3
+ require 'where_lower/base'
4
+ require 'where_lower/scope_spawner'
5
+ require 'where_lower/active_record_extension'
data/spec/spec_helper.rb CHANGED
@@ -1,11 +1,11 @@
1
- require 'coveralls'
2
- Coveralls.wear!('rails')
1
+ if ENV["TRAVIS"]
2
+ require 'coveralls'
3
+ Coveralls.wear!('rails')
4
+ end
3
5
 
4
6
  require 'active_record'
5
7
  require 'where_lower'
6
8
 
7
- require 'squeel'
8
-
9
9
  require 'database_cleaner'
10
10
  require 'logger'
11
11
 
@@ -36,13 +36,13 @@ ActiveRecord::Schema.define(:version => 1) do
36
36
  create_table :parents do |t|
37
37
  t.string :name
38
38
  t.text :description
39
- t.integer :age, null: false, default: 21
39
+ t.integer :age, null: false, default: 0
40
40
  t.boolean :is_minecraft_lover, default: true
41
41
 
42
42
  t.timestamps
43
43
  end
44
44
 
45
- create_table :chirdren do |t|
45
+ create_table :children do |t|
46
46
  t.string :name
47
47
  t.text :description
48
48
  t.integer :age, null: false, default: 0
@@ -52,6 +52,17 @@ ActiveRecord::Schema.define(:version => 1) do
52
52
 
53
53
  t.timestamps
54
54
  end
55
+
56
+ create_table :grand_children do |t|
57
+ t.string :name
58
+ t.text :description
59
+ t.integer :age, null: false, default: 0
60
+ t.boolean :is_minecraft_lover, default: true
61
+
62
+ t.integer :child_id
63
+
64
+ t.timestamps
65
+ end
55
66
  end
56
67
 
57
68
  class ActiveRecord::Base
@@ -66,8 +77,20 @@ class Parent < ActiveRecord::Base
66
77
  has_many :children, inverse_of: :parent
67
78
 
68
79
  scope :latest_first, proc { order('created_at DESC') }
80
+ def self.earliest_first
81
+ order(:created_at)
82
+ end
69
83
  end
70
84
 
71
85
  class Child < ActiveRecord::Base
72
86
  belongs_to :parent, inverse_of: :children, touch: true
87
+ has_many :grand_children, inverse_of: :child
88
+
89
+ validates_presence_of :parent
90
+ end
91
+
92
+ class GrandChild < ActiveRecord::Base
93
+ belongs_to :child, inverse_of: :grand_children, touch: true
94
+
95
+ validates_presence_of :child
73
96
  end
@@ -6,11 +6,28 @@ describe WhereLower do
6
6
  let(:parent_name2) { 'Parent #10' }
7
7
  let(:parent_name3) { 'Parent #20' }
8
8
  let(:parent_description) { 'I need a Medic!' }
9
- let(:parent_description2) { 'I need a Pokemon!' }
9
+ let(:parent_description2) { 'I need a Job!' }
10
+
11
+ let(:child_name) { 'Child' }
12
+ let(:child_name2) { 'Child #10' }
13
+ let(:child_name3) { 'Child #20' }
14
+ let(:child_description) { 'I need a Power Ranger!' }
15
+ let(:child_description2) { 'I need a Pokemon!' }
16
+
17
+ let(:grand_child_name) { 'Grand Child' }
18
+ let(:grand_child_name2) { 'Grand Child #10' }
10
19
 
11
20
  let!(:parent) do
12
21
  Parent.create!(name: parent_name, description: parent_description,
13
- age: 40, is_minecraft_lover: false, created_at: 1.day.ago)
22
+ age: 40, is_minecraft_lover: false, created_at: 40.years.ago)
23
+ end
24
+ let!(:child) do
25
+ Child.create!(name: child_name, description: child_description,
26
+ age: 18, is_minecraft_lover: true, created_at: 18.years.ago, parent: parent)
27
+ end
28
+ let!(:grand_child) do
29
+ GrandChild.create!(name: grand_child_name, description: child_description,
30
+ age: 1, is_minecraft_lover: false, created_at: 1.year.ago, child: child)
14
31
  end
15
32
 
16
33
  describe 'finding records with condition(s) for columns inside the model table' do
@@ -59,8 +76,8 @@ describe WhereLower do
59
76
  Parent.where_lower(name: ('Parenu'..'Parenv')).should be_empty
60
77
  end
61
78
  it 'works like where case insensitively' do
62
- Parent.where_lower(name: (('Parens'.downcase)..('Parenu'.downcase))).should_not be_empty
63
- Parent.where_lower(name: (('Parenu'.downcase)..('Parenv'.downcase))).should be_empty
79
+ Parent.where_lower(name: (('Parens'.swapcase)..('Parenu'.swapcase))).should_not be_empty
80
+ Parent.where_lower(name: (('Parenu'.swapcase)..('Parenv'.swapcase))).should be_empty
64
81
  end
65
82
  end
66
83
 
@@ -120,7 +137,7 @@ describe WhereLower do
120
137
  it 'can be chained with where' do
121
138
  Parent.where_lower(name: parent_name).where(description: parent_description).should_not be_empty
122
139
  end
123
-
140
+
124
141
  it 'can be chained with where_lower' do
125
142
  Parent.where_lower(name: parent_name).where_lower(description: parent_description).should_not be_empty
126
143
  end
@@ -133,15 +150,8 @@ describe WhereLower do
133
150
  it 'can be chained with name scope' do
134
151
  Parent.where_lower(name: parent_name).latest_first.should_not be_empty
135
152
  end
136
-
137
-
138
- it 'can be chained with where in squeel' do
139
- description_value = parent_description
140
- Parent.where_lower(name: parent_name).where{description.eq description_value}.should_not be_empty
141
- end
142
-
143
- it 'can be chained with order in squeel' do
144
- Parent.where_lower(name: parent_name).order{description}.should_not be_empty
153
+ it 'can be chained with class method scope' do
154
+ Parent.where_lower(name: parent_name).earliest_first.should_not be_empty
145
155
  end
146
156
  end
147
157
  end
@@ -150,27 +160,345 @@ describe WhereLower do
150
160
  describe 'finding record using non string columns' do
151
161
  describe 'with type integer' do
152
162
  before do
153
- Parent.where(age: 40).should_not be_empty
154
- Parent.where(age: 41).should be_empty
163
+ Parent.where(age: parent.age).should_not be_empty
164
+ Parent.where(age: parent.age + 1).should be_empty
155
165
  end
156
166
 
157
167
  it 'works like where' do
158
- Parent.where_lower(age: 40).should_not be_empty
159
- Parent.where_lower(age: 41).should be_empty
168
+ Parent.where_lower(age: parent.age).should_not be_empty
169
+ Parent.where_lower(age: parent.age + 1).should be_empty
160
170
  end
161
171
  end
162
172
 
163
173
  describe 'with type boolean' do
164
174
  before do
165
- Parent.where(is_minecraft_lover: false).should_not be_empty
166
- Parent.where(is_minecraft_lover: true).should be_empty
175
+ Parent.where(is_minecraft_lover: parent.is_minecraft_lover).should_not be_empty
176
+ Parent.where(is_minecraft_lover: !parent.is_minecraft_lover).should be_empty
167
177
  end
168
178
 
169
179
  it 'works like where' do
170
- Parent.where_lower(is_minecraft_lover: false).should_not be_empty
171
- Parent.where_lower(is_minecraft_lover: true).should be_empty
180
+ Parent.where_lower(is_minecraft_lover: parent.is_minecraft_lover).should_not be_empty
181
+ Parent.where_lower(is_minecraft_lover: !parent.is_minecraft_lover).should be_empty
172
182
  end
173
183
  end
174
184
  end
175
185
  end
186
+
187
+ describe 'finding records with condition(s) for columns outside the model table' do
188
+ describe 'using string as hash key' do
189
+ describe 'finding record using string column' do
190
+ describe 'with type string' do
191
+ before do
192
+ Parent.joins(:children).where('children.name' => child_name).should_not be_empty
193
+ Parent.joins(:children).where('children.name' => child_name2).should be_empty
194
+ end
195
+
196
+ it 'works like where' do
197
+ Parent.joins(:children).where_lower('children.name' => child_name).should_not be_empty
198
+ Parent.joins(:children).where_lower('children.name' => child_name2).should be_empty
199
+ end
200
+ it 'works like where case insensitively' do
201
+ Parent.joins(:children).where_lower('children.name' => child_name.swapcase).should_not be_empty
202
+ Parent.joins(:children).where_lower('children.name' => child_name2.swapcase).should be_empty
203
+ end
204
+ end
205
+
206
+ describe 'with type text' do
207
+ before do
208
+ Parent.joins(:children).where('children.description' => child_description).should_not be_empty
209
+ Parent.joins(:children).where('children.description' => child_description2).should be_empty
210
+ end
211
+
212
+ it 'works like where' do
213
+ Parent.joins(:children).where_lower('children.description' => child_description).should_not be_empty
214
+ Parent.joins(:children).where_lower('children.description' => child_description2).should be_empty
215
+ end
216
+ it 'works like where case insensitively' do
217
+ Parent.joins(:children).where_lower('children.description' => child_description.swapcase).should_not be_empty
218
+ Parent.joins(:children).where_lower('children.description' => child_description2.swapcase).should be_empty
219
+ end
220
+ end
221
+
222
+ describe 'with different types of values in conditions' do
223
+ describe 'with Range' do
224
+ before do
225
+ Parent.joins(:children).where('children.name' => ('Chilc'..'Chile')).should_not be_empty
226
+ Parent.joins(:children).where('children.name' => ('Chile'..'Chilf')).should be_empty
227
+ end
228
+
229
+ it 'works like where' do
230
+ Parent.joins(:children).where_lower('children.name' => ('Chilc'..'Chile')).should_not be_empty
231
+ Parent.joins(:children).where_lower('children.name' => ('Chile'..'Chilf')).should be_empty
232
+ end
233
+ it 'works like where case insensitively' do
234
+ Parent.joins(:children).where_lower('children.name' => (('Chilc'.swapcase)..('Chile'.swapcase))).should_not be_empty
235
+ Parent.joins(:children).where_lower('children.name' => (('Chile'.swapcase)..('Chilf'.swapcase))).should be_empty
236
+ end
237
+ end
238
+
239
+ describe 'with Array' do
240
+ before do
241
+ Parent.joins(:children).where('children.name' => [child_name, child_name2]).should_not be_empty
242
+ Parent.joins(:children).where('children.name' => [child_name2, child_name3]).should be_empty
243
+ end
244
+
245
+ it 'works like where' do
246
+ Parent.joins(:children).where_lower('children.name' => [child_name, child_name2]).should_not be_empty
247
+ Parent.joins(:children).where_lower('children.name' => [child_name2, child_name3]).should be_empty
248
+ Parent.joins(:children).where_lower('children.name' => []).should be_empty
249
+ end
250
+ it 'works like where case insensitively' do
251
+ Parent.joins(:children).where_lower('children.name' => [child_name.swapcase, child_name2.swapcase]).should_not be_empty
252
+ Parent.joins(:children).where_lower('children.name' => [child_name2.swapcase, child_name3.swapcase]).should be_empty
253
+ Parent.joins(:children).where_lower('children.name' => []).should be_empty
254
+ end
255
+ end
256
+
257
+ describe 'with nil' do
258
+ context 'when record with nil value does not exist' do
259
+ before do
260
+ Parent.joins(:children).where('children.name' => nil).should be_empty
261
+ end
262
+
263
+ it 'works like where' do
264
+ Parent.joins(:children).where_lower('children.name' => nil).should be_empty
265
+ end
266
+ end
267
+ context 'when record with nil value does exist' do
268
+ before do
269
+ Child.create!(name: nil, parent: parent)
270
+ end
271
+
272
+ before do
273
+ Parent.joins(:children).where('children.name' => nil).should_not be_empty
274
+ end
275
+
276
+ it 'works like where' do
277
+ Parent.joins(:children).where_lower('children.name' => nil).should_not be_empty
278
+ end
279
+ end
280
+ end
281
+
282
+ describe 'with query injection' do
283
+ it 'prevents injection' do
284
+ expect do
285
+ Parent.joins(:children).where_lower('children.name' => "'); truncate table parents")
286
+ end.to_not change(Child, :count)
287
+ end
288
+ end
289
+
290
+
291
+ describe 'with chaining' do
292
+ it 'can be chained with where' do
293
+ Parent.joins(:children).where_lower('children.name' => child_name).where('children.description' => child_description).should_not be_empty
294
+ end
295
+
296
+ it 'can be chained with where_lower' do
297
+ Parent.joins(:children).where_lower('children.name' => child_name).where_lower('children.description' => child_description).should_not be_empty
298
+ end
299
+
300
+ it 'can be chained with order' do
301
+ Parent.joins(:children).where_lower('children.name' => child_name).order('children.description').should_not be_empty
302
+ end
303
+
304
+
305
+ it 'can be chained with name scope' do
306
+ Parent.joins(:children).where_lower('children.name' => child_name).latest_first.should_not be_empty
307
+ end
308
+ it 'can be chained with class method scope' do
309
+ Parent.joins(:children).where_lower('children.name' => child_name).earliest_first.should_not be_empty
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ describe 'finding record using non string columns' do
316
+ describe 'with type integer' do
317
+ before do
318
+ Parent.joins(:children).where('children.age' => child.age).should_not be_empty
319
+ Parent.joins(:children).where('children.age' => child.age + 1).should be_empty
320
+ end
321
+
322
+ it 'works like where' do
323
+ Parent.joins(:children).where_lower('children.age' => child.age).should_not be_empty
324
+ Parent.joins(:children).where_lower('children.age' => child.age + 1).should be_empty
325
+ end
326
+ end
327
+
328
+ describe 'with type boolean' do
329
+ before do
330
+ Parent.joins(:children).where('children.is_minecraft_lover' => child.is_minecraft_lover).should_not be_empty
331
+ Parent.joins(:children).where('children.is_minecraft_lover' => !child.is_minecraft_lover).should be_empty
332
+ end
333
+
334
+ it 'works like where' do
335
+ Parent.joins(:children).where_lower('children.is_minecraft_lover' => child.is_minecraft_lover).should_not be_empty
336
+ Parent.joins(:children).where_lower('children.is_minecraft_lover' => !child.is_minecraft_lover).should be_empty
337
+ end
338
+ end
339
+ end
340
+ end
341
+
342
+ describe 'using nested hash' do
343
+ describe 'finding record using string column' do
344
+ describe 'with type string' do
345
+ before do
346
+ Parent.joins(:children).where(children: {name: child_name}).should_not be_empty
347
+ Parent.joins(:children).where(children: {name: child_name2}).should be_empty
348
+ end
349
+
350
+ it 'works like where' do
351
+ Parent.joins(:children).where_lower(children: {name: child_name}).should_not be_empty
352
+ Parent.joins(:children).where_lower(children: {name: child_name2}).should be_empty
353
+ end
354
+ it 'works like where case insensitively' do
355
+ Parent.joins(:children).where_lower(children: {name: child_name.swapcase}).should_not be_empty
356
+ Parent.joins(:children).where_lower(children: {name: child_name2.swapcase}).should be_empty
357
+ end
358
+ end
359
+
360
+ describe 'with type text' do
361
+ before do
362
+ Parent.joins(:children).where(children: {description: child_description}).should_not be_empty
363
+ Parent.joins(:children).where(children: {description: child_description2}).should be_empty
364
+ end
365
+
366
+ it 'works like where' do
367
+ Parent.joins(:children).where_lower(children: {description: child_description}).should_not be_empty
368
+ Parent.joins(:children).where_lower(children: {description: child_description2}).should be_empty
369
+ end
370
+ it 'works like where case insensitively' do
371
+ Parent.joins(:children).where_lower(children: {description: child_description.swapcase}).should_not be_empty
372
+ Parent.joins(:children).where_lower(children: {description: child_description2.swapcase}).should be_empty
373
+ end
374
+ end
375
+
376
+ describe 'with different types of values in conditions' do
377
+ describe 'with Range' do
378
+ before do
379
+ Parent.joins(:children).where(children: {name: ('Chilc'..'Chile')}).should_not be_empty
380
+ Parent.joins(:children).where(children: {name: ('Chile'..'Chilf')}).should be_empty
381
+ end
382
+
383
+ it 'works like where' do
384
+ Parent.joins(:children).where_lower(children: {name: ('Chilc'..'Chile')}).should_not be_empty
385
+ Parent.joins(:children).where_lower(children: {name: ('Chile'..'Chilf')}).should be_empty
386
+ end
387
+ it 'works like where case insensitively' do
388
+ Parent.joins(:children).where_lower(children: {name: (('Chilc'.swapcase)..('Chile'.swapcase))}).should_not be_empty
389
+ Parent.joins(:children).where_lower(children: {name: (('Chile'.swapcase)..('Chilf'.swapcase))}).should be_empty
390
+ end
391
+ end
392
+
393
+ describe 'with Array' do
394
+ before do
395
+ Parent.joins(:children).where(children: {name: [child_name, child_name2]}).should_not be_empty
396
+ Parent.joins(:children).where(children: {name: [child_name2, child_name3]}).should be_empty
397
+ end
398
+
399
+ it 'works like where' do
400
+ Parent.joins(:children).where_lower(children: {name: [child_name, child_name2]}).should_not be_empty
401
+ Parent.joins(:children).where_lower(children: {name: [child_name2, child_name3]}).should be_empty
402
+ Parent.joins(:children).where_lower(children: {name: []}).should be_empty
403
+ end
404
+ it 'works like where case insensitively' do
405
+ Parent.joins(:children).where_lower(children: {name: [child_name.swapcase, child_name2.swapcase]}).should_not be_empty
406
+ Parent.joins(:children).where_lower(children: {name: [child_name2.swapcase, child_name3.swapcase]}).should be_empty
407
+ Parent.joins(:children).where_lower(children: {name: []}).should be_empty
408
+ end
409
+ end
410
+
411
+ describe 'with nil' do
412
+ context 'when record with nil value does not exist' do
413
+ before do
414
+ Parent.joins(:children).where(children: {name: nil}).should be_empty
415
+ end
416
+
417
+ it 'works like where' do
418
+ Parent.joins(:children).where_lower(children: {name: nil}).should be_empty
419
+ end
420
+ end
421
+ context 'when record with nil value does exist' do
422
+ before do
423
+ Child.create!(name: nil, parent: parent)
424
+ end
425
+
426
+ before do
427
+ Parent.joins(:children).where(children: {name: nil}).should_not be_empty
428
+ end
429
+
430
+ it 'works like where' do
431
+ Parent.joins(:children).where_lower(children: {name: nil}).should_not be_empty
432
+ end
433
+ end
434
+ end
435
+
436
+ describe 'with query injection' do
437
+ it 'prevents injection' do
438
+ expect do
439
+ Parent.joins(:children).where_lower(children: {name: "'); truncate table parents"})
440
+ end.to_not change(Child, :count)
441
+ end
442
+ end
443
+
444
+
445
+ describe 'with chaining' do
446
+ it 'can be chained with where' do
447
+ Parent.joins(:children).where_lower(children: {name: child_name}).where(children: {description: child_description}).should_not be_empty
448
+ end
449
+
450
+ it 'can be chained with where_lower' do
451
+ Parent.joins(:children).where_lower(children: {name: child_name}).where_lower(children: {description: child_description}).should_not be_empty
452
+ end
453
+
454
+ it 'can be chained with order' do
455
+ Parent.joins(:children).where_lower(children: {name: child_name}).order('children.description').should_not be_empty
456
+ end
457
+
458
+
459
+ it 'can be chained with name scope' do
460
+ Parent.joins(:children).where_lower(children: {name: child_name}).latest_first.should_not be_empty
461
+ end
462
+ it 'can be chained with class method scope' do
463
+ Parent.joins(:children).where_lower(children: {name: child_name}).earliest_first.should_not be_empty
464
+ end
465
+ end
466
+ end
467
+ end
468
+
469
+ describe 'finding record using non string columns' do
470
+ describe 'with type integer' do
471
+ before do
472
+ Parent.joins(:children).where(children: {age: child.age}).should_not be_empty
473
+ Parent.joins(:children).where(children: {age: child.age + 1}).should be_empty
474
+ end
475
+
476
+ it 'works like where' do
477
+ Parent.joins(:children).where_lower(children: {age: child.age}).should_not be_empty
478
+ Parent.joins(:children).where_lower(children: {age: child.age + 1}).should be_empty
479
+ end
480
+ end
481
+
482
+ describe 'with type boolean' do
483
+ before do
484
+ Parent.joins(:children).where(children: {is_minecraft_lover: child.is_minecraft_lover}).should_not be_empty
485
+ Parent.joins(:children).where(children: {is_minecraft_lover: !child.is_minecraft_lover}).should be_empty
486
+ end
487
+
488
+ it 'works like where' do
489
+ Parent.joins(:children).where_lower(children: {is_minecraft_lover: child.is_minecraft_lover}).should_not be_empty
490
+ Parent.joins(:children).where_lower(children: {is_minecraft_lover: !child.is_minecraft_lover}).should be_empty
491
+ end
492
+ end
493
+ end
494
+
495
+
496
+ describe 'with more than one level deep'
497
+ it 'raises error' do
498
+ expect do
499
+ Parent.joins(children: :grand_children).where_lower(children: {grand_children: {name: grand_child_name}})
500
+ end.to raise_error(WhereLower::TooDeepNestedConditions)
501
+ end
502
+ end
503
+ end
176
504
  end
data/where_lower.gemspec CHANGED
@@ -1,8 +1,8 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
- author_name = 'PikachuEXE'
5
- gem_name = 'where_lower'
4
+ author_name = "PikachuEXE"
5
+ gem_name = "where_lower"
6
6
 
7
7
  require "#{gem_name}/version"
8
8
 
@@ -10,26 +10,32 @@ Gem::Specification.new do |s|
10
10
  s.platform = Gem::Platform::RUBY
11
11
  s.name = gem_name
12
12
  s.version = WhereLower::VERSION
13
- s.summary = 'Provide an easy way to use case insensitive `where` in ActiveRecord.'
14
- s.description = 'ActiveRecord provides no method for case insensitive version of `where` method. So here is one. No longer need to use SQL fragment yeah!'
13
+ s.summary = "Provide an easy way to use case insensitive `where` in ActiveRecord."
14
+ s.description = "ActiveRecord provides no method for case insensitive version of `where` method. So here is one. No longer need to use SQL fragment yeah!"
15
15
 
16
- s.license = 'MIT'
16
+ s.license = "MIT"
17
17
 
18
18
  s.authors = [author_name]
19
- s.email = ['pikachuexe@gmail.com']
19
+ s.email = ["pikachuexe@gmail.com"]
20
20
  s.homepage = "http://github.com/#{author_name}/#{gem_name}"
21
21
 
22
22
  s.files = `git ls-files`.split("\n")
23
23
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
24
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
- s.require_paths = ['lib']
25
+ s.require_paths = ["lib"]
26
26
 
27
- s.add_dependency 'activerecord', '>= 3.1.0', '< 5.0.0'
27
+ s.add_dependency "activerecord", ">= 3.1.0", "< 5.0.0"
28
28
 
29
- s.add_development_dependency 'rake'
30
- s.add_development_dependency 'appraisal'
31
- s.add_development_dependency 'squeel'
32
- s.add_development_dependency 'sqlite3'
33
- s.add_development_dependency 'rspec'
34
- s.add_development_dependency 'database_cleaner'
29
+ s.add_development_dependency "bundler", ">= 1.0.0"
30
+ s.add_development_dependency "rake", ">= 0.9.2"
31
+ s.add_development_dependency "appraisal", ">= 0.5.2"
32
+ s.add_development_dependency "rspec", "~> 2.6"
33
+ s.add_development_dependency "sqlite3", ">= 1.3"
34
+ s.add_development_dependency "database_cleaner", ">= 1.0"
35
+ s.add_development_dependency "coveralls", ">= 0.7"
36
+ s.add_development_dependency "gem-release", ">= 0.7"
37
+
38
+ s.required_ruby_version = ">= 1.9.2"
39
+
40
+ s.required_rubygems_version = ">= 1.4.0"
35
41
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: where_lower
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - PikachuEXE
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-06 00:00:00.000000000 Z
11
+ date: 2014-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -30,90 +30,118 @@ dependencies:
30
30
  - - <
31
31
  - !ruby/object:Gem::Version
32
32
  version: 5.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: 1.0.0
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '>='
45
+ - !ruby/object:Gem::Version
46
+ version: 1.0.0
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: rake
35
49
  requirement: !ruby/object:Gem::Requirement
36
50
  requirements:
37
51
  - - '>='
38
52
  - !ruby/object:Gem::Version
39
- version: '0'
53
+ version: 0.9.2
40
54
  type: :development
41
55
  prerelease: false
42
56
  version_requirements: !ruby/object:Gem::Requirement
43
57
  requirements:
44
58
  - - '>='
45
59
  - !ruby/object:Gem::Version
46
- version: '0'
60
+ version: 0.9.2
47
61
  - !ruby/object:Gem::Dependency
48
62
  name: appraisal
49
63
  requirement: !ruby/object:Gem::Requirement
50
64
  requirements:
51
65
  - - '>='
52
66
  - !ruby/object:Gem::Version
53
- version: '0'
67
+ version: 0.5.2
54
68
  type: :development
55
69
  prerelease: false
56
70
  version_requirements: !ruby/object:Gem::Requirement
57
71
  requirements:
58
72
  - - '>='
59
73
  - !ruby/object:Gem::Version
60
- version: '0'
74
+ version: 0.5.2
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ~>
80
+ - !ruby/object:Gem::Version
81
+ version: '2.6'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ version: '2.6'
61
89
  - !ruby/object:Gem::Dependency
62
- name: squeel
90
+ name: sqlite3
63
91
  requirement: !ruby/object:Gem::Requirement
64
92
  requirements:
65
93
  - - '>='
66
94
  - !ruby/object:Gem::Version
67
- version: '0'
95
+ version: '1.3'
68
96
  type: :development
69
97
  prerelease: false
70
98
  version_requirements: !ruby/object:Gem::Requirement
71
99
  requirements:
72
100
  - - '>='
73
101
  - !ruby/object:Gem::Version
74
- version: '0'
102
+ version: '1.3'
75
103
  - !ruby/object:Gem::Dependency
76
- name: sqlite3
104
+ name: database_cleaner
77
105
  requirement: !ruby/object:Gem::Requirement
78
106
  requirements:
79
107
  - - '>='
80
108
  - !ruby/object:Gem::Version
81
- version: '0'
109
+ version: '1.0'
82
110
  type: :development
83
111
  prerelease: false
84
112
  version_requirements: !ruby/object:Gem::Requirement
85
113
  requirements:
86
114
  - - '>='
87
115
  - !ruby/object:Gem::Version
88
- version: '0'
116
+ version: '1.0'
89
117
  - !ruby/object:Gem::Dependency
90
- name: rspec
118
+ name: coveralls
91
119
  requirement: !ruby/object:Gem::Requirement
92
120
  requirements:
93
121
  - - '>='
94
122
  - !ruby/object:Gem::Version
95
- version: '0'
123
+ version: '0.7'
96
124
  type: :development
97
125
  prerelease: false
98
126
  version_requirements: !ruby/object:Gem::Requirement
99
127
  requirements:
100
128
  - - '>='
101
129
  - !ruby/object:Gem::Version
102
- version: '0'
130
+ version: '0.7'
103
131
  - !ruby/object:Gem::Dependency
104
- name: database_cleaner
132
+ name: gem-release
105
133
  requirement: !ruby/object:Gem::Requirement
106
134
  requirements:
107
135
  - - '>='
108
136
  - !ruby/object:Gem::Version
109
- version: '0'
137
+ version: '0.7'
110
138
  type: :development
111
139
  prerelease: false
112
140
  version_requirements: !ruby/object:Gem::Requirement
113
141
  requirements:
114
142
  - - '>='
115
143
  - !ruby/object:Gem::Version
116
- version: '0'
144
+ version: '0.7'
117
145
  description: ActiveRecord provides no method for case insensitive version of `where`
118
146
  method. So here is one. No longer need to use SQL fragment yeah!
119
147
  email:
@@ -126,6 +154,7 @@ files:
126
154
  - .rspec
127
155
  - .travis.yml
128
156
  - Appraisals
157
+ - CHANGELOG.md
129
158
  - Gemfile
130
159
  - README.md
131
160
  - Rakefile
@@ -133,7 +162,9 @@ files:
133
162
  - gemfiles/rails3_2.gemfile
134
163
  - gemfiles/rails4_0.gemfile
135
164
  - lib/where_lower.rb
136
- - lib/where_lower/core.rb
165
+ - lib/where_lower/active_record_extension.rb
166
+ - lib/where_lower/base.rb
167
+ - lib/where_lower/scope_spawner.rb
137
168
  - lib/where_lower/version.rb
138
169
  - spec/spec_helper.rb
139
170
  - spec/where_lower_spec.rb
@@ -150,15 +181,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
181
  requirements:
151
182
  - - '>='
152
183
  - !ruby/object:Gem::Version
153
- version: '0'
184
+ version: 1.9.2
154
185
  required_rubygems_version: !ruby/object:Gem::Requirement
155
186
  requirements:
156
187
  - - '>='
157
188
  - !ruby/object:Gem::Version
158
- version: '0'
189
+ version: 1.4.0
159
190
  requirements: []
160
191
  rubyforge_project:
161
- rubygems_version: 2.0.7
192
+ rubygems_version: 2.2.1
162
193
  signing_key:
163
194
  specification_version: 4
164
195
  summary: Provide an easy way to use case insensitive `where` in ActiveRecord.
@@ -1,62 +0,0 @@
1
- module WhereLower
2
- module Core
3
- def self.included(base)
4
- base.extend(ClassMethods)
5
- end
6
-
7
- module ClassMethods
8
- def where_lower(fields)
9
- fields.is_a?(Hash) or raise AugumentError, 'fields is not a Hash'
10
-
11
- spawn_lower_scope(fields)
12
- end
13
-
14
- private
15
-
16
- def spawn_lower_scope(fields)
17
- scope = self
18
-
19
- fields.each do |name, value|
20
- scope = spawn_lower_scope_by_type(scope, name, value)
21
- end
22
-
23
- scope
24
- end
25
-
26
- def spawn_lower_scope_by_type(scope, column_name, value)
27
- case value
28
- when Range
29
- value = Range.new(value.begin.to_s.downcase, value.end.to_s.downcase, value.exclude_end?)
30
- scope = scope.where(
31
- lower_query_string(column_name, :in), value
32
- )
33
- when Array # Assume the content to be string, or can be converted to string
34
- value = value.to_a.map {|x| x.to_s.downcase}
35
- scope = scope.where(
36
- lower_query_string(column_name, :in), value
37
- )
38
- when String
39
- value = value.downcase
40
- scope = scope.where(
41
- lower_query_string(column_name), value
42
- )
43
- else # other single value classes
44
- scope = scope.where(column_name => value)
45
- end
46
-
47
- scope
48
- end
49
-
50
- # @param column_name [String/Symbol] name of column
51
- # @param type [Symbol] :in or :eq
52
- def lower_query_string(column_name, type = :eq)
53
- case type
54
- when :in
55
- "lower(#{column_name}) IN (?)"
56
- else #:eq
57
- "lower(#{column_name}) = ?"
58
- end
59
- end
60
- end
61
- end
62
- end