where_lower 0.2.0 → 0.3.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.
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