where_conditioner 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2cbdd592512b031ccd2c5d372c5c523e2397d361
4
+ data.tar.gz: 33e5d99969813c2ec9bb10e986f62e96a79b27b7
5
+ SHA512:
6
+ metadata.gz: dbcb4cc8993e3188161690f895a3ac38ac39212172370b8d656fd6290de211e3026a704036c0ad2188537ab9e540d685bda2b8f55145cc1509ad60c34a5fb806
7
+ data.tar.gz: fa8d21e6d95897283363f2713d17c0b37815c6b3d51895879658859ca4aa82e53c03b0870db63a1f171c57685b3395725a27e4d53912776aa46fc9a39f032a44
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Amitree
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,119 @@
1
+ where\_conditioner
2
+ ==================
3
+
4
+ Here at Amitree, we've found that some of our controllers end up with a lot of
5
+ duplication stemming from the fact that `where` is being invoked conditionally:
6
+
7
+ ```ruby
8
+ purchases = Purchase.all
9
+ purchases = purchases.where('start_date > ?', params[:start_date])
10
+ if params[:start_date].present?
11
+ ```
12
+
13
+ The **where\_conditioner** gem allows us to write more simply:
14
+
15
+ ```ruby
16
+ purchases = Purchase.all
17
+ .where_if_present('start_date > ?', params[:start_date])
18
+ ```
19
+
20
+ See more details on our blog: [http://thesource.amitree.com/2014/04/where-conditioner.html](http://thesource.amitree.com/2014/04/where-conditioner.html).
21
+
22
+ Installation
23
+ -----
24
+
25
+ Simply add to your Gemfile:
26
+
27
+ ```ruby
28
+ gem 'where_conditioner'
29
+ ```
30
+
31
+ Usage
32
+ ----
33
+
34
+ ### where\_if\_present
35
+
36
+ #### ...with a parameterized SQL string
37
+
38
+ Apply the conditions if **all** parameters are non-`nil`:
39
+
40
+ ```ruby
41
+ start_date = Date.yesterday
42
+ end_date = nil
43
+ Purchase.all.where_if_present('date BETWEEN ? and ?', start_date, end_date)
44
+ # => Purchase.all
45
+
46
+ start_date = Date.yesterday
47
+ end_date = Date.today
48
+ Purchase.all.where_if_present('date BETWEEN ? and ?', start_date, end_date)
49
+ # => Purchase.where('date BETWEEN ? and ?', start_date, end_date)
50
+ ```
51
+
52
+ #### ...with a hash
53
+
54
+ Apply only those conditions where the value is non-`nil`:
55
+
56
+ ```ruby
57
+ status = 'shipped'
58
+ customer_id = nil
59
+ Purchase.all.where_if_present(status: status, customer_id: customer_id)
60
+ # => Purchase.all.where(status: 'shipped')
61
+ ```
62
+
63
+ ### if
64
+
65
+ ```ruby
66
+ show_pending = false
67
+ Purchase.all
68
+ .if(!show_pending)
69
+ .where(status: 'shipped')
70
+ # => Purchase.all.where(status: 'shipped')
71
+
72
+ show_pending = true
73
+ Purchase.all
74
+ .if(!show_pending)
75
+ .where(status: 'shipped')
76
+ # => Purchase.all
77
+ ```
78
+
79
+ ### unless
80
+
81
+ Like `if`, but the opposite! :)
82
+
83
+ ```ruby
84
+ show_pending = false
85
+ Purchase.all
86
+ .unless(show_pending)
87
+ .where(status: 'shipped')
88
+ # => Purchase.all.where(status: 'shipped')
89
+
90
+ show_pending = true
91
+ Purchase.all
92
+ .unless(show_pending)
93
+ .where(status: 'shipped')
94
+ # => Purchase.all
95
+ ```
96
+
97
+ ### else
98
+
99
+ ```ruby
100
+ wacky_order = false
101
+ Purchase.all
102
+ .if(wacky_order)
103
+ .order(customer_id: :desc)
104
+ .else
105
+ .order(:date)
106
+ # => Purchase.all.order(:date)
107
+ ```
108
+
109
+ ### Passing blocks
110
+
111
+ If the result of a condition is more than one method call, you can pass in a
112
+ block instead:
113
+
114
+ ```ruby
115
+ recent = false
116
+ Purchase.all
117
+ .if(recent) { where('date > ?', 30.days.ago).order(date: :desc) }
118
+ .else { where.not('date > ?', 30.days.ago).order(:date) }
119
+ ```
@@ -0,0 +1,68 @@
1
+ module WhereConditioner
2
+ class NullSink < BasicObject
3
+ def initialize obj
4
+ @obj = obj
5
+ end
6
+
7
+ def method_missing symbol, *args
8
+ @obj
9
+ end
10
+ end
11
+
12
+ extend ActiveSupport::Concern
13
+
14
+ def if condition, &block
15
+ @last_condition = condition
16
+ conditional @last_condition, &block
17
+ end
18
+
19
+ def unless condition, &block
20
+ self.if !condition, &block
21
+ end
22
+
23
+ def else &block
24
+ conditional !@last_condition, &block
25
+ end
26
+
27
+ def where_if_present *args
28
+ if Hash === args.first && args.length == 1
29
+ hash = args.first.compact
30
+ if hash.present?
31
+ self.where hash
32
+ else
33
+ self
34
+ end
35
+ elsif String === args.first
36
+ unless args.include? nil
37
+ self.where *args
38
+ else
39
+ self
40
+ end
41
+ else
42
+ self.where *args
43
+ end
44
+ end
45
+
46
+ private
47
+ def conditional condition, &block
48
+ if block_given?
49
+ if condition
50
+ self.instance_eval &block
51
+ else
52
+ self
53
+ end
54
+ else
55
+ if condition
56
+ self
57
+ else
58
+ NullSink.new(self)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ module ActiveRecord
65
+ class Relation
66
+ include WhereConditioner
67
+ end
68
+ end
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+
3
+ describe WhereConditioner do
4
+ let(:relation) { ActiveRecord::Relation.new }
5
+
6
+ describe '#where_if_present' do
7
+ context 'with a parameterized SQL string' do
8
+ it 'calls where() if all values are present' do
9
+ expect(relation).to receive(:where).with('version BETWEEN ? AND ?', 1, 2)
10
+ relation.where_if_present('version BETWEEN ? AND ?', 1, 2)
11
+ end
12
+
13
+ it 'does not call where(), and returns self, if some values are missing' do
14
+ expect(relation).not_to receive(:where)
15
+ expect(relation.where_if_present('version BETWEEN ? AND ?', nil, 2)).to eq relation
16
+ end
17
+ end
18
+
19
+ context 'with a hash' do
20
+ it 'calls where() with all non-nil values' do
21
+ expect(relation).to receive(:where).with(key1: '', key3: 'value', key4: 0)
22
+ relation.where_if_present(key1: '', key2: nil, key3: 'value', key4: 0)
23
+ end
24
+
25
+ it 'does not call where(), and returns self, if all values are nil' do
26
+ expect(relation).not_to receive(:where)
27
+ relation.where_if_present(key1: nil, key2: nil)
28
+ end
29
+ end
30
+ end
31
+
32
+ describe '#if' do
33
+ context 'chained' do
34
+ context 'with true condition' do
35
+ it 'passes method calls to relation' do
36
+ expect(relation).to receive_message_chain(:foo, :bar)
37
+ relation.if(true).foo.bar
38
+ end
39
+ end
40
+
41
+ context 'with false condition' do
42
+ it 'passes method calls to relation' do
43
+ expect(relation).to receive_message_chain(:bar)
44
+ relation.if(false).foo.bar
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'with a block' do
50
+ context 'with true condition' do
51
+ it 'passes method calls to relation' do
52
+ expect(relation).to receive_message_chain(:foo, :bar, :baz)
53
+ relation.if(true) { foo.bar }.baz
54
+ end
55
+ end
56
+
57
+ context 'with false condition' do
58
+ it 'passes method calls to relation' do
59
+ expect(relation).to receive_message_chain(:baz)
60
+ relation.if(false) { foo.bar }.baz
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ describe '#unless' do
67
+ context 'chained' do
68
+ context 'with true condition' do
69
+ it 'passes method calls to relation' do
70
+ expect(relation).to receive_message_chain(:bar)
71
+ relation.unless(true).foo.bar
72
+ end
73
+ end
74
+
75
+ context 'with false condition' do
76
+ it 'passes method calls to relation' do
77
+ expect(relation).to receive_message_chain(:foo, :bar)
78
+ relation.unless(false).foo.bar
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'with a block' do
84
+ context 'with true condition' do
85
+ it 'passes method calls to relation' do
86
+ expect(relation).to receive_message_chain(:baz)
87
+ relation.unless(true) { foo.bar }.baz
88
+ end
89
+ end
90
+
91
+ context 'with false condition' do
92
+ it 'passes method calls to relation' do
93
+ expect(relation).to receive_message_chain(:foo, :bar, :baz)
94
+ relation.unless(false) { foo.bar }.baz
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#else' do
101
+ context 'chained' do
102
+ context 'with true condition' do
103
+ it 'passes method calls to relation' do
104
+ expect(relation).to receive(:foo).and_return(relation)
105
+ expect(relation).to receive(:baz)
106
+ relation.if(true).foo.else.bar.baz
107
+ end
108
+ end
109
+
110
+ context 'with false condition' do
111
+ it 'passes method calls to relation' do
112
+ expect(relation).to receive_message_chain(:bar, :baz)
113
+ relation.if(false).foo.else.bar.baz
114
+ end
115
+ end
116
+ end
117
+
118
+ context 'with a block' do
119
+ context 'with true condition' do
120
+ it 'passes method calls to relation' do
121
+ expect(relation).to receive(:foo).and_return(relation)
122
+ expect(relation).to receive(:moop)
123
+ relation.if(true).foo.else { bar.baz }.moop
124
+ end
125
+ end
126
+
127
+ context 'with false condition' do
128
+ it 'passes method calls to relation' do
129
+ expect(relation).to receive_message_chain(:bar, :baz, :moop)
130
+ relation.if(false).foo.else { bar.baz }.moop
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,4 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext'
3
+
4
+ require 'where_conditioner'
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: where_conditioner
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Tony Novak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0.beta
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0.beta
41
+ description: Where Conditioner allows you to write conditional `where` expressions
42
+ in a DRY manner.
43
+ email: tony@amitree.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - Gemfile
49
+ - LICENSE
50
+ - README.md
51
+ - lib/where_conditioner.rb
52
+ - spec/lib/where_conditioner_spec.rb
53
+ - spec/spec_helper.rb
54
+ homepage: http://rubygems.org/gems/where_conditioner
55
+ licenses:
56
+ - MIT
57
+ metadata: {}
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubyforge_project:
74
+ rubygems_version: 2.2.1
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: Where Conditioner
78
+ test_files: []