bitmask_attributes 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/.travis.yml +4 -0
- data/CHANGELOG.rdoc +59 -6
- data/Gemfile +2 -6
- data/Gemfile.lock +17 -17
- data/README.rdoc +62 -2
- data/lib/bitmask_attributes.rb +1 -1
- data/lib/bitmask_attributes/definition.rb +40 -26
- data/lib/bitmask_attributes/version.rb +1 -1
- data/test/bitmask_attributes_test.rb +231 -175
- data/test/support/models.rb +32 -8
- metadata +11 -7
- data/test/support/helpers.rb +0 -17
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/CHANGELOG.rdoc
CHANGED
@@ -1,14 +1,67 @@
|
|
1
|
+
=== Version 0.3.0 - 2012-04-30 21:36:18 +0100
|
2
|
+
|
3
|
+
Aaron Hamid (1):
|
4
|
+
* wrap two scopes in procs to fix migration/startup issues
|
5
|
+
|
6
|
+
Andre Duffeck (1):
|
7
|
+
* fix values_for
|
8
|
+
|
9
|
+
Ivan Buiko (1):
|
10
|
+
* Adding retrieval by exact value
|
11
|
+
|
12
|
+
Joel Moss (9):
|
13
|
+
* Merge pull request #6 from incandescent/fix-scope-proc
|
14
|
+
* Merge pull request #4 from SUSE/fix_values_for
|
15
|
+
* Bundle update
|
16
|
+
* Adding retrieval by exact value
|
17
|
+
* Merge branch 'retrieval-by-exact-value'
|
18
|
+
* Resolved merge conflict
|
19
|
+
* Merge pull request #9 from numerex/master
|
20
|
+
* Merge pull request #13 from spemmons/master
|
21
|
+
* Merge pull request #16 from spemmons/master
|
22
|
+
|
23
|
+
Sebastian Borrazas (1):
|
24
|
+
* Not raising exception when the bitmask column is not found, since its adding it on a migration
|
25
|
+
|
26
|
+
spemmons (22):
|
27
|
+
* detect whether or not nulls are possible with an attribute and remove OR conditions checking if it is null or not
|
28
|
+
* ignore RubyMine info
|
29
|
+
* merge changes with joel moss for scopes
|
30
|
+
* check for model not being ready when creating scopes
|
31
|
+
* support nulls with an explicit attribute, not by inspecting the model due to preloading issues
|
32
|
+
* add documentation for the :null option
|
33
|
+
* support multiple values in the "without_..." scope that excludes match for of any of the bits; previously 2nd and following values were ignored
|
34
|
+
* update documentation to reflect support for multiple arguments to "without_..."
|
35
|
+
* update documentation to reflect support for multiple arguments to "without_..."
|
36
|
+
* update documentation to reflect support for multiple arguments to "without_..."
|
37
|
+
* added tests to verify the validity of zykadelic's complaint that "with_" scopes were improperly including "OR IS NOT NULL" and fixed the code by removing the condition in this case
|
38
|
+
* DRY-up scope expressions, standardizing on "no_" and "bitmask_for_...(*values)" instead of repeating more complex calculations; also found another example of zykadelic's complaint with "with_any_" that is fixed and has tests
|
39
|
+
* add travis configuration file
|
40
|
+
* follow buffpojken in forcing string-interpolated model to work in global scope
|
41
|
+
* Merge branch 'master' of github.com:spemmons/bitmask_attributes
|
42
|
+
* remove out-dated helpers that, although no longer used, were being loaded by Travis-CI and failing
|
43
|
+
* clarify travis config to fix failing CI build that works locally
|
44
|
+
* apparently 1.9.2 doesn't work... will figure out why later
|
45
|
+
* add :zero option and tests
|
46
|
+
* check for a string match instead of a symbol to allow values arriving from web forms and services to match properly
|
47
|
+
* added documentation for the :zero attribute
|
48
|
+
* rename :zero to :zero_value for clarity
|
49
|
+
|
50
|
+
steve emmons (3):
|
51
|
+
* Merge pull request #1 from numerex/master
|
52
|
+
* Merge pull request #2 from numerex/master
|
53
|
+
* Merge pull request #15 from spemmons/master
|
54
|
+
|
55
|
+
|
1
56
|
=== Version 0.2.4 - 2011-11-23 09:37:50 +0000
|
2
57
|
|
3
58
|
Ivan Buiko (3):
|
4
|
-
*
|
5
|
-
* latest versions in Gemfile.lock
|
6
|
-
* don't validate attribute during creating table
|
59
|
+
* No longer validates attribute during migrations
|
7
60
|
|
8
61
|
Joel Moss (3):
|
9
|
-
*
|
10
|
-
*
|
11
|
-
|
62
|
+
* Added changeling tasks for help with gem releases
|
63
|
+
* Created docs using sdoc
|
64
|
+
|
12
65
|
|
13
66
|
|
14
67
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,40 +1,40 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
bitmask_attributes (0.2.
|
4
|
+
bitmask_attributes (0.2.4)
|
5
5
|
activerecord (~> 3.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: http://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (3.
|
11
|
-
activesupport (= 3.
|
10
|
+
activemodel (3.2.2)
|
11
|
+
activesupport (= 3.2.2)
|
12
12
|
builder (~> 3.0.0)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
arel (~> 2.2.1)
|
13
|
+
activerecord (3.2.2)
|
14
|
+
activemodel (= 3.2.2)
|
15
|
+
activesupport (= 3.2.2)
|
16
|
+
arel (~> 3.0.2)
|
18
17
|
tzinfo (~> 0.3.29)
|
19
|
-
activesupport (3.
|
18
|
+
activesupport (3.2.2)
|
19
|
+
i18n (~> 0.6)
|
20
20
|
multi_json (~> 1.0)
|
21
|
-
ansi (1.4.
|
22
|
-
arel (
|
21
|
+
ansi (1.4.2)
|
22
|
+
arel (3.0.2)
|
23
23
|
builder (3.0.0)
|
24
24
|
i18n (0.6.0)
|
25
|
-
json (1.6.
|
26
|
-
multi_json (1.0
|
25
|
+
json (1.6.5)
|
26
|
+
multi_json (1.1.0)
|
27
27
|
rake (0.9.2.2)
|
28
|
-
rdoc (3.
|
28
|
+
rdoc (3.12)
|
29
29
|
json (~> 1.4)
|
30
30
|
sdoc (0.3.16)
|
31
31
|
json (>= 1.1.3)
|
32
32
|
rdoc (~> 3.10)
|
33
33
|
shoulda (2.11.3)
|
34
|
-
sqlite3 (1.3.
|
34
|
+
sqlite3 (1.3.5)
|
35
35
|
turn (0.8.3)
|
36
36
|
ansi
|
37
|
-
tzinfo (0.3.
|
37
|
+
tzinfo (0.3.32)
|
38
38
|
|
39
39
|
PLATFORMS
|
40
40
|
ruby
|
@@ -44,5 +44,5 @@ DEPENDENCIES
|
|
44
44
|
rake
|
45
45
|
sdoc
|
46
46
|
shoulda
|
47
|
-
sqlite3 (>= 1.3.
|
47
|
+
sqlite3 (>= 1.3.5)
|
48
48
|
turn
|
data/README.rdoc
CHANGED
@@ -71,6 +71,10 @@ A couple useful named scopes are also generated when you use
|
|
71
71
|
# => (all users who are BOTH editors and writers)
|
72
72
|
User.with_any_roles(:editor, :writer)
|
73
73
|
# => (all users who are editors OR writers)
|
74
|
+
User.with_exact_roles(:writer)
|
75
|
+
# => (all users who are ONLY writers)
|
76
|
+
User.with_exact_roles(:writer, :editor)
|
77
|
+
# => (all users who are BOTH editors and writers and nothing else)
|
74
78
|
|
75
79
|
Find records without any bitmask set:
|
76
80
|
|
@@ -79,12 +83,16 @@ Find records without any bitmask set:
|
|
79
83
|
User.no_roles
|
80
84
|
# => (all users without a role)
|
81
85
|
|
82
|
-
Find records without
|
86
|
+
Find records without specific attributes:
|
83
87
|
|
84
88
|
User.without_roles(:editor)
|
85
89
|
# => (all users who are not editors)
|
86
90
|
|
87
|
-
|
91
|
+
User.without_roles(:writer, :editor)
|
92
|
+
# => (all users who are NEITHER writers nor editors)
|
93
|
+
|
94
|
+
Note that "without_" supports one or more attribute arguments, and the "no_" method does not support arguments.
|
95
|
+
And "with_exact_" without arguments is alias for "no_"
|
88
96
|
|
89
97
|
|
90
98
|
=== Adding Methods
|
@@ -102,6 +110,58 @@ named scopes):
|
|
102
110
|
user.other_attribute.worked?
|
103
111
|
# => true
|
104
112
|
|
113
|
+
=== Handling null values
|
114
|
+
|
115
|
+
By default, bitmasks support the potential for the underlying integer value to be null. However, if you have created
|
116
|
+
a field that is guaranteed never to be null, you can simplify the SQL query conditions by declaring ":null => false"
|
117
|
+
in the definition:
|
118
|
+
|
119
|
+
bitmask :never_null_attributes,:as => [:value1, :value2], :null => false
|
120
|
+
|
121
|
+
=== Allowing for a "zero" value
|
122
|
+
|
123
|
+
It is common to use web forms to set bitmask bits using checkboxes. If the various bits each are represented by a
|
124
|
+
checkbox and the user unchecks them all, the resulting "params" posted to the controller will be missing. When this
|
125
|
+
happens, a controller will need to ensure that a "params" hash entry has an empty array or a call to "update_attributes"
|
126
|
+
will not change the attribute. For example:
|
127
|
+
|
128
|
+
In model...
|
129
|
+
class SomeModel < ActiveRecord::Base
|
130
|
+
bitmask :some_attribute, :as => [:value1, :value2]
|
131
|
+
end
|
132
|
+
|
133
|
+
In view...
|
134
|
+
<input type="checkbox" name="some_model[some_attribute][]" value="value1"/>
|
135
|
+
<input type="checkbox" name="some_model[some_attribute][]" value="value2"/>
|
136
|
+
|
137
|
+
In controller...
|
138
|
+
def update
|
139
|
+
@some_model = SomeModel.find(params[:id])
|
140
|
+
params[:some_attribute] ||= []
|
141
|
+
@some_model.update_attributes(params)
|
142
|
+
end
|
143
|
+
|
144
|
+
As an alternative, you may provide a special symbol representing "zero":
|
145
|
+
|
146
|
+
In model...
|
147
|
+
class SomeModel < ActiveRecord::Base
|
148
|
+
bitmask :some_attribute, :as => [:value1, :value2], :zero_value => :none
|
149
|
+
end
|
150
|
+
|
151
|
+
In view...
|
152
|
+
<input type="checkbox" name="some_model[some_attribute][]" value="value1"/>
|
153
|
+
<input type="checkbox" name="some_model[some_attribute][]" value="value2"/>
|
154
|
+
<input type="hidden" name="some_model[some_attribute][]" value="none"/>
|
155
|
+
|
156
|
+
In controller...
|
157
|
+
def update
|
158
|
+
@some_model = SomeModel.find(params[:id])
|
159
|
+
@some_model.update_attributes(params)
|
160
|
+
end
|
161
|
+
|
162
|
+
This technique can be particularly useful for both forms and web services where setting the attribute in question may
|
163
|
+
be optionally included or not such that the controller setting of an empty array in the first example would not be
|
164
|
+
correct.
|
105
165
|
|
106
166
|
=== Warning: Modifying possible values
|
107
167
|
|
data/lib/bitmask_attributes.rb
CHANGED
@@ -9,7 +9,7 @@ module BitmaskAttributes
|
|
9
9
|
unless options[:as] && options[:as].kind_of?(Array)
|
10
10
|
raise ArgumentError, "Must provide an Array :as option"
|
11
11
|
end
|
12
|
-
bitmask_definitions[attribute] = Definition.new(attribute, options[:as].to_a,
|
12
|
+
bitmask_definitions[attribute] = Definition.new(attribute, options[:as].to_a,options[:null].nil? || options[:null],options[:zero_value],&extension)
|
13
13
|
bitmask_definitions[attribute].install_on(self)
|
14
14
|
end
|
15
15
|
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module BitmaskAttributes
|
2
2
|
class Definition
|
3
|
-
attr_reader :attribute, :values, :extension
|
3
|
+
attr_reader :attribute, :values, :allow_null, :zero_value, :extension
|
4
4
|
|
5
|
-
def initialize(attribute, values=[], &extension)
|
5
|
+
def initialize(attribute, values=[],allow_null = true,zero_value = nil, &extension)
|
6
6
|
@attribute = attribute
|
7
7
|
@values = values
|
8
8
|
@extension = extension
|
9
|
+
@allow_null = allow_null
|
10
|
+
@zero_value = zero_value
|
9
11
|
end
|
10
12
|
|
11
13
|
def install_on(model)
|
@@ -17,7 +19,7 @@ module BitmaskAttributes
|
|
17
19
|
create_scopes_on model
|
18
20
|
create_attribute_methods_on model
|
19
21
|
end
|
20
|
-
|
22
|
+
|
21
23
|
private
|
22
24
|
|
23
25
|
def validate_for(model)
|
@@ -25,9 +27,9 @@ module BitmaskAttributes
|
|
25
27
|
# database (the migration has not been run) or table doesn't exist. This usually
|
26
28
|
# occurs in the 'test' and 'production' environment or during migration.
|
27
29
|
return if defined?(Rails) && Rails.configuration.cache_classes || !model.table_exists?
|
28
|
-
|
30
|
+
|
29
31
|
unless model.columns.detect { |col| col.name == attribute.to_s }
|
30
|
-
|
32
|
+
Rails.logger.warn "WARNING: `#{attribute}' is not an attribute of `#{model}'. But, it's ok if it happens during migrations and your \"bitmasked\" attribute is still not created."
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -56,7 +58,7 @@ module BitmaskAttributes
|
|
56
58
|
model.class_eval %(
|
57
59
|
def #{attribute}=(raw_value)
|
58
60
|
values = raw_value.kind_of?(Array) ? raw_value : [raw_value]
|
59
|
-
self.#{attribute}.replace(values.reject(
|
61
|
+
self.#{attribute}.replace(values.reject{|value| #{eval_string_for_zero('value')}})
|
60
62
|
end
|
61
63
|
)
|
62
64
|
end
|
@@ -74,7 +76,9 @@ module BitmaskAttributes
|
|
74
76
|
model.class_eval %(
|
75
77
|
def self.bitmask_for_#{attribute}(*values)
|
76
78
|
values.inject(0) do |bitmask, value|
|
77
|
-
|
79
|
+
if #{eval_string_for_zero('value')}
|
80
|
+
bit = 0
|
81
|
+
elsif (bit = bitmasks[:#{attribute}][value]).nil?
|
78
82
|
raise ArgumentError, "Unsupported value for #{attribute}: \#{value.inspect}"
|
79
83
|
end
|
80
84
|
bitmask | bit
|
@@ -105,50 +109,60 @@ module BitmaskAttributes
|
|
105
109
|
end
|
106
110
|
|
107
111
|
def create_scopes_on(model)
|
112
|
+
or_is_null_condition = " OR #{attribute} IS NULL" if allow_null
|
113
|
+
|
108
114
|
model.class_eval %(
|
109
115
|
scope :with_#{attribute},
|
110
116
|
proc { |*values|
|
111
117
|
if values.blank?
|
112
|
-
where('#{attribute} > 0
|
118
|
+
where('#{attribute} > 0')
|
113
119
|
else
|
114
120
|
sets = values.map do |value|
|
115
|
-
mask =
|
121
|
+
mask = ::#{model}.bitmask_for_#{attribute}(value)
|
116
122
|
"#{attribute} & \#{mask} <> 0"
|
117
123
|
end
|
118
124
|
where(sets.join(' AND '))
|
119
125
|
end
|
120
126
|
}
|
121
127
|
scope :without_#{attribute},
|
122
|
-
proc { |
|
123
|
-
if
|
124
|
-
|
125
|
-
where("#{attribute} IS NULL OR #{attribute} & ? = 0", mask)
|
128
|
+
proc { |*values|
|
129
|
+
if values.blank?
|
130
|
+
no_#{attribute}
|
126
131
|
else
|
127
|
-
where("#{attribute}
|
128
|
-
end
|
129
|
-
|
130
|
-
|
131
|
-
scope :
|
132
|
+
where("#{attribute} & ? = 0#{or_is_null_condition}", ::#{model}.bitmask_for_#{attribute}(*values))
|
133
|
+
end
|
134
|
+
}
|
135
|
+
|
136
|
+
scope :with_exact_#{attribute},
|
137
|
+
proc { | *values|
|
138
|
+
if values.blank?
|
139
|
+
no_#{attribute}
|
140
|
+
else
|
141
|
+
where("#{attribute} = ?", ::#{model}.bitmask_for_#{attribute}(*values))
|
142
|
+
end
|
143
|
+
}
|
132
144
|
|
145
|
+
scope :no_#{attribute}, proc { where("#{attribute} = 0#{or_is_null_condition}") }
|
146
|
+
|
133
147
|
scope :with_any_#{attribute},
|
134
148
|
proc { |*values|
|
135
149
|
if values.blank?
|
136
|
-
where('#{attribute} > 0
|
150
|
+
where('#{attribute} > 0')
|
137
151
|
else
|
138
|
-
|
139
|
-
mask = #{model}.bitmask_for_#{attribute}(value)
|
140
|
-
"#{attribute} & \#{mask} <> 0"
|
141
|
-
end
|
142
|
-
where(sets.join(' OR '))
|
152
|
+
where("#{attribute} & ? <> 0", ::#{model}.bitmask_for_#{attribute}(*values))
|
143
153
|
end
|
144
154
|
}
|
145
155
|
)
|
146
156
|
values.each do |value|
|
147
157
|
model.class_eval %(
|
148
158
|
scope :#{attribute}_for_#{value},
|
149
|
-
where('#{attribute} & ? <> 0',
|
159
|
+
proc { where('#{attribute} & ? <> 0', ::#{model}.bitmask_for_#{attribute}(:#{value})) }
|
150
160
|
)
|
151
161
|
end
|
152
162
|
end
|
163
|
+
|
164
|
+
def eval_string_for_zero(value_string)
|
165
|
+
zero_value ? "#{value_string}.blank? || #{value_string}.to_s == '#{zero_value}'" : "#{value_string}.blank?"
|
166
|
+
end
|
153
167
|
end
|
154
|
-
end
|
168
|
+
end
|
@@ -1,219 +1,275 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class BitmaskAttributesTest < ActiveSupport::TestCase
|
4
|
-
|
5
|
-
context "Campaign" do
|
6
|
-
teardown do
|
7
|
-
Company.destroy_all
|
8
|
-
Campaign.destroy_all
|
9
|
-
end
|
10
|
-
|
11
|
-
should "return all defined values of a given bitmask attribute" do
|
12
|
-
assert_equal Campaign.values_for_medium, [:web, :print, :email, :phone]
|
13
|
-
end
|
14
|
-
|
15
|
-
should "can assign single value to bitmask" do
|
16
|
-
assert_stored Campaign.new(:medium => :web), :web
|
17
|
-
end
|
18
4
|
|
19
|
-
|
20
|
-
|
21
|
-
|
5
|
+
def self.context_with_classes(label,campaign_class,company_class)
|
6
|
+
context label do
|
7
|
+
setup do
|
8
|
+
@campaign_class = campaign_class
|
9
|
+
@company_class = company_class
|
10
|
+
end
|
22
11
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
assert_stored campaign, :web, :print, :phone
|
28
|
-
end
|
12
|
+
teardown do
|
13
|
+
@company_class.destroy_all
|
14
|
+
@campaign_class.destroy_all
|
15
|
+
end
|
29
16
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
campaign.medium << :phone
|
34
|
-
assert_stored campaign, :web, :print, :phone
|
35
|
-
campaign.medium << :phone
|
36
|
-
assert_stored campaign, :web, :print, :phone
|
37
|
-
campaign.medium << "phone"
|
38
|
-
assert_stored campaign, :web, :print, :phone
|
39
|
-
assert_equal 1, campaign.medium.select { |value| value == :phone }.size
|
40
|
-
assert_equal 0, campaign.medium.select { |value| value == "phone" }.size
|
41
|
-
end
|
17
|
+
should "return all defined values of a given bitmask attribute" do
|
18
|
+
assert_equal @campaign_class.values_for_medium, [:web, :print, :email, :phone]
|
19
|
+
end
|
42
20
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
campaign.medium = [:phone, :email]
|
47
|
-
assert_stored campaign, :phone, :email
|
48
|
-
end
|
21
|
+
should "can assign single value to bitmask" do
|
22
|
+
assert_stored @campaign_class.new(:medium => :web), :web
|
23
|
+
end
|
49
24
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
assert campaign.save
|
54
|
-
assert_stored Campaign.find(campaign.id), :web, :print
|
55
|
-
end
|
25
|
+
should "can assign multiple values to bitmask" do
|
26
|
+
assert_stored @campaign_class.new(:medium => [:web, :print]), :web, :print
|
27
|
+
end
|
56
28
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
campaign.medium
|
29
|
+
should "can add single value to bitmask" do
|
30
|
+
campaign = @campaign_class.new(:medium => [:web, :print])
|
31
|
+
assert_stored campaign, :web, :print
|
32
|
+
campaign.medium << :phone
|
33
|
+
assert_stored campaign, :web, :print, :phone
|
61
34
|
end
|
62
|
-
|
63
|
-
|
35
|
+
|
36
|
+
should "ignores duplicate values added to bitmask" do
|
37
|
+
campaign = @campaign_class.new(:medium => [:web, :print])
|
38
|
+
assert_stored campaign, :web, :print
|
39
|
+
campaign.medium << :phone
|
40
|
+
assert_stored campaign, :web, :print, :phone
|
41
|
+
campaign.medium << :phone
|
42
|
+
assert_stored campaign, :web, :print, :phone
|
43
|
+
campaign.medium << "phone"
|
44
|
+
assert_stored campaign, :web, :print, :phone
|
45
|
+
assert_equal 1, campaign.medium.select { |value| value == :phone }.size
|
46
|
+
assert_equal 0, campaign.medium.select { |value| value == "phone" }.size
|
64
47
|
end
|
65
|
-
assert campaign.misc.worked?
|
66
|
-
end
|
67
48
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
49
|
+
should "can assign new values at once to bitmask" do
|
50
|
+
campaign = @campaign_class.new(:medium => [:web, :print])
|
51
|
+
assert_stored campaign, :web, :print
|
52
|
+
campaign.medium = [:phone, :email]
|
53
|
+
assert_stored campaign, :phone, :email
|
54
|
+
end
|
74
55
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
should "assert use of unknown value in convenience method will result in exception" do
|
84
|
-
assert_unsupported { Campaign.bitmask_for_medium(:web, :and_this_isnt_valid) }
|
85
|
-
end
|
56
|
+
should "can save bitmask to db and retrieve values transparently" do
|
57
|
+
campaign = @campaign_class.new(:medium => [:web, :print])
|
58
|
+
assert_stored campaign, :web, :print
|
59
|
+
assert campaign.save
|
60
|
+
assert_stored @campaign_class.find(campaign.id), :web, :print
|
61
|
+
end
|
86
62
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
63
|
+
should "can add custom behavor to value proxies during bitmask definition" do
|
64
|
+
campaign = @campaign_class.new(:medium => [:web, :print])
|
65
|
+
assert_raises NoMethodError do
|
66
|
+
campaign.medium.worked?
|
67
|
+
end
|
68
|
+
assert_nothing_raised do
|
69
|
+
campaign.misc.worked?
|
70
|
+
end
|
71
|
+
assert campaign.misc.worked?
|
91
72
|
end
|
92
|
-
assert_equal Campaign.bitmask_for_medium(:web, :print), string_bit
|
93
|
-
end
|
94
73
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
74
|
+
should "cannot use unsupported values" do
|
75
|
+
assert_unsupported { @campaign_class.new(:medium => [:web, :print, :this_will_fail]) }
|
76
|
+
campaign = @campaign_class.new(:medium => :web)
|
77
|
+
assert_unsupported { campaign.medium << :this_will_fail_also }
|
78
|
+
assert_unsupported { campaign.medium = [:so_will_this] }
|
79
|
+
end
|
100
80
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
81
|
+
should "can determine bitmasks using convenience method" do
|
82
|
+
assert @campaign_class.bitmask_for_medium(:web, :print)
|
83
|
+
assert_equal(
|
84
|
+
@campaign_class.bitmasks[:medium][:web] | @campaign_class.bitmasks[:medium][:print],
|
85
|
+
@campaign_class.bitmask_for_medium(:web, :print)
|
86
|
+
)
|
87
|
+
end
|
108
88
|
|
109
|
-
|
110
|
-
|
111
|
-
assert @campaign.medium_for_web?
|
112
|
-
assert @campaign.medium_for_print?
|
113
|
-
assert !@campaign.medium_for_email?
|
114
|
-
end
|
115
|
-
|
116
|
-
should "be supported by the simple predicate method" do
|
117
|
-
assert @campaign.medium?(:web)
|
118
|
-
assert @campaign.medium?(:print)
|
119
|
-
assert !@campaign.medium?(:email)
|
120
|
-
end
|
89
|
+
should "assert use of unknown value in convenience method will result in exception" do
|
90
|
+
assert_unsupported { @campaign_class.bitmask_for_medium(:web, :and_this_isnt_valid) }
|
121
91
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
assert
|
92
|
+
|
93
|
+
should "hash of values is with indifferent access" do
|
94
|
+
string_bit = nil
|
95
|
+
assert_nothing_raised do
|
96
|
+
assert (string_bit = @campaign_class.bitmask_for_medium('web', 'print'))
|
127
97
|
end
|
98
|
+
assert_equal @campaign_class.bitmask_for_medium(:web, :print), string_bit
|
128
99
|
end
|
129
|
-
end
|
130
100
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
@campaign2 = @company.campaigns.create
|
136
|
-
@campaign3 = @company.campaigns.create :medium => [:web, :email]
|
101
|
+
should "save bitmask with non-standard attribute names" do
|
102
|
+
campaign = @campaign_class.new(:Legacy => [:upper, :case])
|
103
|
+
assert campaign.save
|
104
|
+
assert_equal [:upper, :case], @campaign_class.find(campaign.id).Legacy
|
137
105
|
end
|
138
106
|
|
139
|
-
should "
|
140
|
-
assert_equal
|
107
|
+
should "ignore blanks fed as values" do
|
108
|
+
assert_equal 0b11,@campaign_class.bitmask_for_medium(:web, :print, '')
|
109
|
+
campaign = @campaign_class.new(:medium => [:web, :print, ''])
|
110
|
+
assert_stored campaign, :web, :print
|
141
111
|
end
|
142
112
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
113
|
+
context "checking" do
|
114
|
+
setup { @campaign = @campaign_class.new(:medium => [:web, :print]) }
|
115
|
+
|
116
|
+
context "for a single value" do
|
117
|
+
should "be supported by an attribute_for_value convenience method" do
|
118
|
+
assert @campaign.medium_for_web?
|
119
|
+
assert @campaign.medium_for_print?
|
120
|
+
assert !@campaign.medium_for_email?
|
121
|
+
end
|
122
|
+
|
123
|
+
should "be supported by the simple predicate method" do
|
124
|
+
assert @campaign.medium?(:web)
|
125
|
+
assert @campaign.medium?(:print)
|
126
|
+
assert !@campaign.medium?(:email)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context "for multiple values" do
|
131
|
+
should "be supported by the simple predicate method" do
|
132
|
+
assert @campaign.medium?(:web, :print)
|
133
|
+
assert !@campaign.medium?(:web, :email)
|
134
|
+
end
|
135
|
+
end
|
149
136
|
end
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
137
|
+
|
138
|
+
context "named scopes" do
|
139
|
+
setup do
|
140
|
+
@company = @company_class.create(:name => "Test Co, Intl.")
|
141
|
+
@campaign1 = @company.campaigns.create :medium => [:web, :print]
|
142
|
+
@campaign2 = @company.campaigns.create
|
143
|
+
@campaign3 = @company.campaigns.create :medium => [:web, :email]
|
144
|
+
@campaign4 = @company.campaigns.create :medium => [:web]
|
145
|
+
@campaign5 = @company.campaigns.create :medium => [:web, :print, :email]
|
146
|
+
@campaign6 = @company.campaigns.create :medium => [:web, :print, :email, :phone]
|
147
|
+
@campaign7 = @company.campaigns.create :medium => [:email, :phone]
|
148
|
+
end
|
149
|
+
|
150
|
+
should "support retrieval by any value" do
|
151
|
+
assert_equal [@campaign1, @campaign3, @campaign4, @campaign5, @campaign6, @campaign7], @company.campaigns.with_medium
|
152
|
+
end
|
153
|
+
|
154
|
+
should "support retrieval by one matching value" do
|
155
|
+
assert_equal [@campaign1, @campaign5, @campaign6], @company.campaigns.with_medium(:print)
|
156
|
+
end
|
157
|
+
|
158
|
+
should "support retrieval by any matching value (OR)" do
|
159
|
+
assert_equal [@campaign1, @campaign3, @campaign5, @campaign6, @campaign7], @company.campaigns.with_any_medium(:print, :email)
|
160
|
+
end
|
161
|
+
|
162
|
+
should "support retrieval by all matching values" do
|
163
|
+
assert_equal [@campaign1, @campaign5, @campaign6], @company.campaigns.with_medium(:web, :print)
|
164
|
+
assert_equal [@campaign3, @campaign5, @campaign6], @company.campaigns.with_medium(:web, :email)
|
165
|
+
end
|
166
|
+
|
167
|
+
should "support retrieval for no values" do
|
168
|
+
assert_equal [@campaign2], @company.campaigns.without_medium
|
169
|
+
assert_equal [@campaign2], @company.campaigns.no_medium
|
170
|
+
end
|
171
|
+
|
172
|
+
should "support retrieval without a specific value" do
|
173
|
+
assert_equal [@campaign2, @campaign3, @campaign4, @campaign7], @company.campaigns.without_medium(:print)
|
174
|
+
assert_equal [@campaign2, @campaign7], @company.campaigns.without_medium(:web, :print)
|
175
|
+
assert_equal [@campaign2, @campaign3, @campaign4], @company.campaigns.without_medium(:print, :phone)
|
176
|
+
end
|
177
|
+
|
178
|
+
should "support retrieval by exact value" do
|
179
|
+
assert_equal [@campaign4], @company.campaigns.with_exact_medium(:web)
|
180
|
+
assert_equal [@campaign1], @company.campaigns.with_exact_medium(:web, :print)
|
181
|
+
assert_equal [@campaign2], @company.campaigns.with_exact_medium
|
182
|
+
end
|
183
|
+
|
184
|
+
should "not retrieve retrieve a subsequent zero value for an unqualified with scope " do
|
185
|
+
assert_equal [@campaign1, @campaign3, @campaign4, @campaign5, @campaign6, @campaign7], @company.campaigns.with_medium
|
186
|
+
@campaign4.medium = []
|
187
|
+
@campaign4.save
|
188
|
+
assert_equal [@campaign1, @campaign3, @campaign5, @campaign6, @campaign7], @company.campaigns.with_medium
|
189
|
+
assert_equal [@campaign1, @campaign3, @campaign5, @campaign6, @campaign7], @company.campaigns.with_any_medium
|
190
|
+
end
|
191
|
+
|
192
|
+
should "not retrieve retrieve a subsequent zero value for a qualified with scope " do
|
193
|
+
assert_equal [@campaign1, @campaign3, @campaign4, @campaign5, @campaign6], @company.campaigns.with_medium(:web)
|
194
|
+
@campaign4.medium = []
|
195
|
+
@campaign4.save
|
196
|
+
assert_equal [@campaign1, @campaign3, @campaign5, @campaign6], @company.campaigns.with_medium(:web)
|
197
|
+
assert_equal [@campaign1, @campaign3, @campaign5, @campaign6], @company.campaigns.with_any_medium(:web)
|
198
|
+
end
|
154
199
|
end
|
155
200
|
|
156
|
-
should "
|
157
|
-
|
158
|
-
|
201
|
+
should "can check if at least one value is set" do
|
202
|
+
campaign = @campaign_class.new(:medium => [:web, :print])
|
203
|
+
assert campaign.medium?
|
204
|
+
|
205
|
+
campaign = @campaign_class.new
|
206
|
+
assert !campaign.medium?
|
159
207
|
end
|
160
|
-
|
161
|
-
should "
|
162
|
-
|
208
|
+
|
209
|
+
should "find by bitmask values" do
|
210
|
+
campaign = @campaign_class.new(:medium => [:web, :print])
|
211
|
+
assert campaign.save
|
212
|
+
|
213
|
+
assert_equal(
|
214
|
+
@campaign_class.find(:all, :conditions => ['medium & ? <> 0', @campaign_class.bitmask_for_medium(:print)]),
|
215
|
+
@campaign_class.medium_for_print
|
216
|
+
)
|
217
|
+
|
218
|
+
assert_equal @campaign_class.medium_for_print.first, @campaign_class.medium_for_print.medium_for_web.first
|
219
|
+
|
220
|
+
assert_equal [], @campaign_class.medium_for_email
|
221
|
+
assert_equal [], @campaign_class.medium_for_web.medium_for_email
|
163
222
|
end
|
164
|
-
end
|
165
223
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
campaign = Campaign.new
|
171
|
-
assert !campaign.medium?
|
172
|
-
end
|
224
|
+
should "find no values" do
|
225
|
+
campaign = @campaign_class.create(:medium => [:web, :print])
|
226
|
+
assert campaign.save
|
173
227
|
|
174
|
-
|
175
|
-
campaign = Campaign.new(:medium => [:web, :print])
|
176
|
-
assert campaign.save
|
177
|
-
|
178
|
-
assert_equal(
|
179
|
-
Campaign.find(:all, :conditions => ['medium & ? <> 0', Campaign.bitmask_for_medium(:print)]),
|
180
|
-
Campaign.medium_for_print
|
181
|
-
)
|
182
|
-
|
183
|
-
assert_equal Campaign.medium_for_print.first, Campaign.medium_for_print.medium_for_web.first
|
184
|
-
|
185
|
-
assert_equal [], Campaign.medium_for_email
|
186
|
-
assert_equal [], Campaign.medium_for_web.medium_for_email
|
187
|
-
end
|
228
|
+
assert_equal [], @campaign_class.no_medium
|
188
229
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
campaign.medium = []
|
196
|
-
assert campaign.save
|
197
|
-
|
198
|
-
assert_equal [campaign], Campaign.no_medium
|
199
|
-
end
|
230
|
+
campaign.medium = []
|
231
|
+
assert campaign.save
|
232
|
+
|
233
|
+
assert_equal [campaign], @campaign_class.no_medium
|
234
|
+
end
|
200
235
|
|
236
|
+
should "allow zero in values without changing result" do
|
237
|
+
assert_equal 0,@campaign_class.bitmask_for_allow_zero(:none)
|
238
|
+
assert_equal 0b111,@campaign_class.bitmask_for_allow_zero(:one,:two,:three,:none)
|
201
239
|
|
202
|
-
|
240
|
+
campaign = @campaign_class.new(:allow_zero => :none)
|
241
|
+
assert campaign.save
|
242
|
+
assert_equal [],campaign.allow_zero
|
203
243
|
|
204
|
-
|
205
|
-
|
244
|
+
campaign.allow_zero = :none
|
245
|
+
assert campaign.save
|
246
|
+
assert_equal [],campaign.allow_zero
|
247
|
+
|
248
|
+
campaign.allow_zero = [:one,:none]
|
249
|
+
assert campaign.save
|
250
|
+
assert_equal [:one],campaign.allow_zero
|
206
251
|
end
|
207
252
|
|
208
|
-
|
209
|
-
|
210
|
-
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
def assert_unsupported(&block)
|
257
|
+
assert_raises(ArgumentError, &block)
|
211
258
|
end
|
212
|
-
|
213
|
-
|
259
|
+
|
260
|
+
def assert_stored(record, *values)
|
261
|
+
values.each do |value|
|
262
|
+
assert record.medium.any? { |v| v.to_s == value.to_s }, "Values #{record.medium.inspect} does not include #{value.inspect}"
|
263
|
+
end
|
264
|
+
full_mask = values.inject(0) do |mask, value|
|
265
|
+
mask | @campaign_class.bitmasks[:medium][value]
|
266
|
+
end
|
267
|
+
assert_equal full_mask, record.medium.to_i
|
214
268
|
end
|
215
|
-
assert_equal full_mask, record.medium.to_i
|
216
|
-
end
|
217
269
|
|
270
|
+
end
|
218
271
|
end
|
272
|
+
|
273
|
+
context_with_classes 'Campaign with null attributes',CampaignWithNull,CompanyWithNull
|
274
|
+
context_with_classes 'Campaign without null attributes',CampaignWithoutNull,CompanyWithoutNull
|
219
275
|
end
|
data/test/support/models.rb
CHANGED
@@ -1,26 +1,50 @@
|
|
1
1
|
ActiveRecord::Schema.define do
|
2
|
-
create_table :
|
2
|
+
create_table :campaign_with_nulls do |t|
|
3
3
|
t.integer :company_id
|
4
|
-
t.integer :medium, :misc, :Legacy
|
4
|
+
t.integer :medium, :allow_zero, :misc, :Legacy
|
5
5
|
end
|
6
|
-
create_table :
|
6
|
+
create_table :company_with_nulls do |t|
|
7
|
+
t.string :name
|
8
|
+
end
|
9
|
+
create_table :campaign_without_nulls do |t|
|
10
|
+
t.integer :company_id
|
11
|
+
t.integer :medium, :allow_zero, :misc, :Legacy, :null => false, :default => 0
|
12
|
+
end
|
13
|
+
create_table :company_without_nulls do |t|
|
7
14
|
t.string :name
|
8
15
|
end
|
9
16
|
end
|
10
17
|
|
18
|
+
# Pseudo models for testing purposes
|
11
19
|
|
12
|
-
class
|
13
|
-
has_many :campaigns
|
20
|
+
class CompanyWithNull < ActiveRecord::Base
|
21
|
+
has_many :campaigns,:class_name => 'CampaignWithNull',:foreign_key => 'company_id'
|
14
22
|
end
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
belongs_to :company
|
24
|
+
class CampaignWithNull < ActiveRecord::Base
|
25
|
+
belongs_to :company,:class_name => 'CompanyWithNull'
|
19
26
|
bitmask :medium, :as => [:web, :print, :email, :phone]
|
27
|
+
bitmask :allow_zero, :as => [:one, :two, :three], :zero_value => :none
|
20
28
|
bitmask :misc, :as => %w(some useless values) do
|
21
29
|
def worked?
|
22
30
|
true
|
23
31
|
end
|
24
32
|
end
|
25
33
|
bitmask :Legacy, :as => [:upper, :case]
|
34
|
+
end
|
35
|
+
|
36
|
+
class CompanyWithoutNull < ActiveRecord::Base
|
37
|
+
has_many :campaigns,:class_name => 'CampaignWithoutNull',:foreign_key => 'company_id'
|
38
|
+
end
|
39
|
+
|
40
|
+
class CampaignWithoutNull < ActiveRecord::Base
|
41
|
+
belongs_to :company,:class_name => 'CompanyWithoutNull'
|
42
|
+
bitmask :medium, :as => [:web, :print, :email, :phone], :null => false
|
43
|
+
bitmask :allow_zero, :as => [:one, :two, :three], :zero_value => :none, :null => false
|
44
|
+
bitmask :misc, :as => %w(some useless values), :null => false do
|
45
|
+
def worked?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
bitmask :Legacy, :as => [:upper, :case], :null => false
|
26
50
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitmask_attributes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-04-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,7 +21,12 @@ dependencies:
|
|
21
21
|
version: '3.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
25
30
|
description:
|
26
31
|
email: joel@developwithstyle.com
|
27
32
|
executables: []
|
@@ -31,6 +36,7 @@ files:
|
|
31
36
|
- .document
|
32
37
|
- .gitignore
|
33
38
|
- .rvmrc
|
39
|
+
- .travis.yml
|
34
40
|
- CHANGELOG.rdoc
|
35
41
|
- Gemfile
|
36
42
|
- Gemfile.lock
|
@@ -75,7 +81,6 @@ files:
|
|
75
81
|
- lib/bitmask_attributes/value_proxy.rb
|
76
82
|
- lib/bitmask_attributes/version.rb
|
77
83
|
- test/bitmask_attributes_test.rb
|
78
|
-
- test/support/helpers.rb
|
79
84
|
- test/support/models.rb
|
80
85
|
- test/test_helper.rb
|
81
86
|
homepage: http://github.com/joelmoss/bitmask_attributes
|
@@ -98,12 +103,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
103
|
version: '0'
|
99
104
|
requirements: []
|
100
105
|
rubyforge_project:
|
101
|
-
rubygems_version: 1.8.
|
106
|
+
rubygems_version: 1.8.23
|
102
107
|
signing_key:
|
103
108
|
specification_version: 3
|
104
109
|
summary: Simple bitmask attribute support for ActiveRecord
|
105
110
|
test_files:
|
106
111
|
- test/bitmask_attributes_test.rb
|
107
|
-
- test/support/helpers.rb
|
108
112
|
- test/support/models.rb
|
109
113
|
- test/test_helper.rb
|
data/test/support/helpers.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
class ActiveSupport::TestCase
|
2
|
-
|
3
|
-
def assert_unsupported(&block)
|
4
|
-
assert_raises(ArgumentError, &block)
|
5
|
-
end
|
6
|
-
|
7
|
-
def assert_stored(record, *values)
|
8
|
-
values.each do |value|
|
9
|
-
assert record.medium.any? { |v| v.to_s == value.to_s }, "Values #{record.medium.inspect} does not include #{value.inspect}"
|
10
|
-
end
|
11
|
-
full_mask = values.inject(0) do |mask, value|
|
12
|
-
mask | Campaign.bitmasks[:medium][value]
|
13
|
-
end
|
14
|
-
assert_equal full_mask, record.medium.to_i
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|