clean-bitmask-attribute 2.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.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.markdown +121 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/lib/bitmask-attribute.rb +2 -0
- data/lib/bitmask_attribute/attribute.rb +25 -0
- data/lib/bitmask_attribute/core_ext/blank.rb +77 -0
- data/lib/bitmask_attribute/core_ext/hash_with_indifferent_access.rb +182 -0
- data/lib/bitmask_attribute/core_ext/returning.rb +43 -0
- data/lib/bitmask_attribute/value_proxy.rb +77 -0
- data/lib/bitmask_attribute.rb +162 -0
- data/rails/init.rb +3 -0
- data/test/bitmask_attribute_test.rb +223 -0
- data/test/clean_bitmask_attribute_test.rb +155 -0
- data/test/clean_test_helper.rb +42 -0
- data/test/test_helper.rb +69 -0
- metadata +130 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'bitmask_attribute/value_proxy'
|
2
|
+
require 'bitmask_attribute/attribute'
|
3
|
+
require 'bitmask_attribute/core_ext/hash_with_indifferent_access' unless defined?(HashWithIndifferentAccess)
|
4
|
+
require 'bitmask_attribute/core_ext/returning' unless Object.respond_to?(:returning)
|
5
|
+
require 'bitmask_attribute/core_ext/blank' unless Object.respond_to?(:blank?)
|
6
|
+
|
7
|
+
module BitmaskAttribute
|
8
|
+
|
9
|
+
class Definition
|
10
|
+
|
11
|
+
attr_reader :attribute, :values, :extension
|
12
|
+
def initialize(attribute, values=[], &extension)
|
13
|
+
@attribute = attribute
|
14
|
+
@values = values
|
15
|
+
@extension = extension
|
16
|
+
end
|
17
|
+
|
18
|
+
def install_on(model)
|
19
|
+
validate_for model
|
20
|
+
generate_bitmasks_on model
|
21
|
+
override model
|
22
|
+
create_convenience_class_method_on(model)
|
23
|
+
create_convenience_instance_methods_on(model)
|
24
|
+
create_named_scopes_on(model) if defined?(ActiveRecord::Base) && model.respond_to?(scope_method)
|
25
|
+
end
|
26
|
+
|
27
|
+
#######
|
28
|
+
private
|
29
|
+
#######
|
30
|
+
|
31
|
+
def validate_for(model)
|
32
|
+
unless (model.columns.detect { |col| col.name == attribute.to_s } rescue true)
|
33
|
+
raise ArgumentError, "`#{attribute}' is not an attribute of `#{model}'"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def generate_bitmasks_on(model)
|
38
|
+
model.bitmasks[attribute] = returning HashWithIndifferentAccess.new do |mapping|
|
39
|
+
values.each_with_index do |value, index|
|
40
|
+
mapping[value] = 0b1 << index
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def override(model)
|
46
|
+
override_getter_on(model)
|
47
|
+
override_setter_on(model)
|
48
|
+
end
|
49
|
+
|
50
|
+
def override_getter_on(model)
|
51
|
+
model.class_eval %(
|
52
|
+
def #{attribute}
|
53
|
+
@#{attribute} ||= BitmaskAttribute::ValueProxy.new(self, :#{attribute}, &self.class.bitmask_definitions[:#{attribute}].extension)
|
54
|
+
end
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def override_setter_on(model)
|
59
|
+
model.class_eval %(
|
60
|
+
def #{attribute}=(raw_value)
|
61
|
+
values = raw_value.kind_of?(Array) ? raw_value : [raw_value]
|
62
|
+
self.#{attribute}.replace(values.reject(&:blank?))
|
63
|
+
end
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_convenience_class_method_on(model)
|
68
|
+
model.class_eval %(
|
69
|
+
def self.bitmask_for_#{attribute}(*values)
|
70
|
+
values.inject(0) do |bitmask, value|
|
71
|
+
unless (bit = bitmasks[:#{attribute}][value])
|
72
|
+
raise ArgumentError, "Unsupported value for #{attribute}: \#{value.inspect}"
|
73
|
+
end
|
74
|
+
bitmask | bit
|
75
|
+
end
|
76
|
+
end
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def create_convenience_instance_methods_on(model)
|
82
|
+
values.each do |value|
|
83
|
+
model.class_eval %(
|
84
|
+
def #{attribute}_for_#{value}?
|
85
|
+
self.#{attribute}?(:#{value})
|
86
|
+
end
|
87
|
+
)
|
88
|
+
end
|
89
|
+
model.class_eval %(
|
90
|
+
def #{attribute}?(*values)
|
91
|
+
if !values.blank?
|
92
|
+
values.all? do |value|
|
93
|
+
self.#{attribute}.include?(value.to_sym)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
self.#{attribute}.present?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
def scope_method
|
103
|
+
ActiveRecord::VERSION::STRING >= "3" ? :scope : :named_scope
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_named_scopes_on(model)
|
107
|
+
model.class_eval %(
|
108
|
+
#{scope_method} :with_#{attribute},
|
109
|
+
proc { |*values|
|
110
|
+
if values.blank?
|
111
|
+
{:conditions => '#{attribute} > 0 OR #{attribute} IS NOT NULL'}
|
112
|
+
else
|
113
|
+
sets = values.map do |value|
|
114
|
+
mask = #{model}.bitmask_for_#{attribute}(value)
|
115
|
+
"#{attribute} & \#{mask} <> 0"
|
116
|
+
end
|
117
|
+
{:conditions => sets.join(' AND ')}
|
118
|
+
end
|
119
|
+
}
|
120
|
+
#{scope_method} :without_#{attribute}, :conditions => "#{attribute} == 0 OR #{attribute} IS NULL"
|
121
|
+
#{scope_method} :no_#{attribute}, :conditions => "#{attribute} == 0 OR #{attribute} IS NULL"
|
122
|
+
)
|
123
|
+
values.each do |value|
|
124
|
+
model.class_eval %(
|
125
|
+
#{scope_method} :#{attribute}_for_#{value},
|
126
|
+
:conditions => ['#{attribute} & ? <> 0', #{model}.bitmask_for_#{attribute}(:#{value})]
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.included(model)
|
134
|
+
model.extend ClassMethods
|
135
|
+
# Include basic attributes support for clean class
|
136
|
+
# TODO improve attributes detection
|
137
|
+
model.send(:include, BitmaskAttribute::Attribute) unless model.included_modules.detect{|m| m.to_s.include? 'AttributeMethods'}
|
138
|
+
end
|
139
|
+
|
140
|
+
module ClassMethods
|
141
|
+
|
142
|
+
def bitmask(attribute, options={}, &extension)
|
143
|
+
unless options[:as] && options[:as].kind_of?(Array)
|
144
|
+
raise ArgumentError, "Must provide an Array :as option"
|
145
|
+
end
|
146
|
+
bitmask_definitions[attribute] = BitmaskAttribute::Definition.new(attribute, options[:as].to_a, &extension)
|
147
|
+
bitmask_definitions[attribute].install_on(self)
|
148
|
+
end
|
149
|
+
|
150
|
+
def bitmask_definitions
|
151
|
+
@bitmask_definitions ||= {}
|
152
|
+
end
|
153
|
+
|
154
|
+
def bitmasks
|
155
|
+
@bitmasks ||= {}
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
ActiveRecord::Base.send :include, BitmaskAttribute if defined? ActiveRecord::Base
|
data/rails/init.rb
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class BitmaskAttributeTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Campaign" do
|
6
|
+
|
7
|
+
teardown do
|
8
|
+
Company.destroy_all
|
9
|
+
Campaign.destroy_all
|
10
|
+
end
|
11
|
+
|
12
|
+
should "can assign single value to bitmask" do
|
13
|
+
assert_stored Campaign.new(:medium => :web), :web
|
14
|
+
end
|
15
|
+
|
16
|
+
should "can assign multiple values to bitmask" do
|
17
|
+
assert_stored Campaign.new(:medium => [:web, :print]), :web, :print
|
18
|
+
end
|
19
|
+
|
20
|
+
should "can add single value to bitmask" do
|
21
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
22
|
+
assert_stored campaign, :web, :print
|
23
|
+
campaign.medium << :phone
|
24
|
+
assert_stored campaign, :web, :print, :phone
|
25
|
+
end
|
26
|
+
|
27
|
+
should "ignores duplicate values added to bitmask" do
|
28
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
29
|
+
assert_stored campaign, :web, :print
|
30
|
+
campaign.medium << :phone
|
31
|
+
assert_stored campaign, :web, :print, :phone
|
32
|
+
campaign.medium << :phone
|
33
|
+
assert_stored campaign, :web, :print, :phone
|
34
|
+
assert_equal 1, campaign.medium.select { |value| value == :phone }.size
|
35
|
+
end
|
36
|
+
|
37
|
+
should "can assign new values at once to bitmask" do
|
38
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
39
|
+
assert_stored campaign, :web, :print
|
40
|
+
campaign.medium = [:phone, :email]
|
41
|
+
assert_stored campaign, :phone, :email
|
42
|
+
end
|
43
|
+
|
44
|
+
should "can save bitmask to db and retrieve values transparently" do
|
45
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
46
|
+
assert_stored campaign, :web, :print
|
47
|
+
assert campaign.save
|
48
|
+
assert_stored Campaign.find(campaign.id), :web, :print
|
49
|
+
end
|
50
|
+
|
51
|
+
should "can add custom behavor to value proxies during bitmask definition" do
|
52
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
53
|
+
assert_raises NoMethodError do
|
54
|
+
campaign.medium.worked?
|
55
|
+
end
|
56
|
+
assert_nothing_raised do
|
57
|
+
campaign.misc.worked?
|
58
|
+
end
|
59
|
+
assert campaign.misc.worked?
|
60
|
+
end
|
61
|
+
|
62
|
+
should "cannot use unsupported values" do
|
63
|
+
assert_unsupported { Campaign.new(:medium => [:web, :print, :this_will_fail]) }
|
64
|
+
campaign = Campaign.new(:medium => :web)
|
65
|
+
assert_unsupported { campaign.medium << :this_will_fail_also }
|
66
|
+
assert_unsupported { campaign.medium = [:so_will_this] }
|
67
|
+
end
|
68
|
+
|
69
|
+
should "can determine bitmasks using convenience method" do
|
70
|
+
assert Campaign.bitmask_for_medium(:web, :print)
|
71
|
+
assert_equal(
|
72
|
+
Campaign.bitmasks[:medium][:web] | Campaign.bitmasks[:medium][:print],
|
73
|
+
Campaign.bitmask_for_medium(:web, :print)
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
should "assert use of unknown value in convenience method will result in exception" do
|
78
|
+
assert_unsupported { Campaign.bitmask_for_medium(:web, :and_this_isnt_valid) }
|
79
|
+
end
|
80
|
+
|
81
|
+
should "hash of values is with indifferent access" do
|
82
|
+
string_bit = nil
|
83
|
+
assert_nothing_raised do
|
84
|
+
assert (string_bit = Campaign.bitmask_for_medium('web', 'print'))
|
85
|
+
end
|
86
|
+
assert_equal Campaign.bitmask_for_medium(:web, :print), string_bit
|
87
|
+
end
|
88
|
+
|
89
|
+
should "save bitmask with non-standard attribute names" do
|
90
|
+
campaign = Campaign.new(:Legacy => [:upper, :case])
|
91
|
+
assert campaign.save
|
92
|
+
assert_equal [:upper, :case], Campaign.find(campaign.id).Legacy
|
93
|
+
end
|
94
|
+
|
95
|
+
should "ignore blanks fed as values" do
|
96
|
+
campaign = Campaign.new(:medium => [:web, :print, ''])
|
97
|
+
assert_stored campaign, :web, :print
|
98
|
+
end
|
99
|
+
|
100
|
+
should "convert values passed as strings to symbols" do
|
101
|
+
campaign = Campaign.new
|
102
|
+
campaign.medium << "web"
|
103
|
+
assert_equal [:web], campaign.medium
|
104
|
+
assert_equal true, campaign.medium?("web")
|
105
|
+
end
|
106
|
+
|
107
|
+
context "checking" do
|
108
|
+
|
109
|
+
setup { @campaign = Campaign.new(:medium => [:web, :print]) }
|
110
|
+
|
111
|
+
context "for a single value" do
|
112
|
+
|
113
|
+
should "be supported by an attribute_for_value convenience method" do
|
114
|
+
assert @campaign.medium_for_web?
|
115
|
+
assert @campaign.medium_for_print?
|
116
|
+
assert !@campaign.medium_for_email?
|
117
|
+
end
|
118
|
+
|
119
|
+
should "be supported by the simple predicate method" do
|
120
|
+
assert @campaign.medium?(:web)
|
121
|
+
assert @campaign.medium?(:print)
|
122
|
+
assert !@campaign.medium?(:email)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
context "for multiple values" do
|
128
|
+
|
129
|
+
should "be supported by the simple predicate method" do
|
130
|
+
assert @campaign.medium?(:web, :print)
|
131
|
+
assert !@campaign.medium?(:web, :email)
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
context "named scopes" do
|
139
|
+
|
140
|
+
setup do
|
141
|
+
@company = Company.create(:name => "Test Co, Intl.")
|
142
|
+
@campaign1 = @company.campaigns.create :medium => [:web, :print]
|
143
|
+
@campaign2 = @company.campaigns.create
|
144
|
+
@campaign3 = @company.campaigns.create :medium => [:web, :email]
|
145
|
+
end
|
146
|
+
|
147
|
+
should "support retrieval by any value" do
|
148
|
+
assert_equal [@campaign1, @campaign3], @company.campaigns.with_medium
|
149
|
+
end
|
150
|
+
|
151
|
+
should "support retrieval by one matching value" do
|
152
|
+
assert_equal [@campaign1], @company.campaigns.with_medium(:print)
|
153
|
+
end
|
154
|
+
|
155
|
+
should "support retrieval by all matching values" do
|
156
|
+
assert_equal [@campaign1], @company.campaigns.with_medium(:web, :print)
|
157
|
+
assert_equal [@campaign3], @company.campaigns.with_medium(:web, :email)
|
158
|
+
end
|
159
|
+
|
160
|
+
should "support retrieval for no values" do
|
161
|
+
assert_equal [@campaign2], @company.campaigns.without_medium
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
should "can check if at least one value is set" do
|
167
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
168
|
+
|
169
|
+
assert campaign.medium?
|
170
|
+
|
171
|
+
campaign = Campaign.new
|
172
|
+
|
173
|
+
assert !campaign.medium?
|
174
|
+
end
|
175
|
+
|
176
|
+
should "find by bitmask values" do
|
177
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
178
|
+
assert campaign.save
|
179
|
+
|
180
|
+
assert_equal(
|
181
|
+
Campaign.find(:all, :conditions => ['medium & ? <> 0', Campaign.bitmask_for_medium(:print)]),
|
182
|
+
Campaign.medium_for_print
|
183
|
+
)
|
184
|
+
|
185
|
+
assert_equal Campaign.medium_for_print.all, Campaign.medium_for_print.medium_for_web.all
|
186
|
+
|
187
|
+
assert_equal [], Campaign.medium_for_email
|
188
|
+
assert_equal [], Campaign.medium_for_web.medium_for_email
|
189
|
+
end
|
190
|
+
|
191
|
+
should "find no values" do
|
192
|
+
campaign = Campaign.create(:medium => [:web, :print])
|
193
|
+
assert campaign.save
|
194
|
+
|
195
|
+
assert_equal [], Campaign.no_medium
|
196
|
+
|
197
|
+
campaign.medium = []
|
198
|
+
assert campaign.save
|
199
|
+
|
200
|
+
assert_equal [campaign], Campaign.no_medium
|
201
|
+
end
|
202
|
+
|
203
|
+
#######
|
204
|
+
private
|
205
|
+
#######
|
206
|
+
|
207
|
+
def assert_unsupported(&block)
|
208
|
+
assert_raises(ArgumentError, &block)
|
209
|
+
end
|
210
|
+
|
211
|
+
def assert_stored(record, *values)
|
212
|
+
values.each do |value|
|
213
|
+
assert record.medium.any? { |v| v.to_s == value.to_s }, "Values #{record.medium.inspect} does not include #{value.inspect}"
|
214
|
+
end
|
215
|
+
full_mask = values.inject(0) do |mask, value|
|
216
|
+
mask | Campaign.bitmasks[:medium][value]
|
217
|
+
end
|
218
|
+
assert_equal full_mask, record.medium.to_i
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'clean_test_helper'
|
2
|
+
|
3
|
+
class CleanBitmaskAttributeTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "CleanCampaign" do
|
6
|
+
|
7
|
+
should "can assign single value to bitmask" do
|
8
|
+
assert_stored CleanCampaign.new(:medium => :web), :web
|
9
|
+
end
|
10
|
+
|
11
|
+
should "can assign multiple values to bitmask" do
|
12
|
+
assert_stored CleanCampaign.new(:medium => [:web, :print]), :web, :print
|
13
|
+
end
|
14
|
+
|
15
|
+
should "can add single value to bitmask" do
|
16
|
+
campaign = CleanCampaign.new(:medium => [:web, :print])
|
17
|
+
assert_stored campaign, :web, :print
|
18
|
+
campaign.medium << :phone
|
19
|
+
assert_stored campaign, :web, :print, :phone
|
20
|
+
end
|
21
|
+
|
22
|
+
should "ignores duplicate values added to bitmask" do
|
23
|
+
campaign = CleanCampaign.new(:medium => [:web, :print])
|
24
|
+
assert_stored campaign, :web, :print
|
25
|
+
campaign.medium << :phone
|
26
|
+
assert_stored campaign, :web, :print, :phone
|
27
|
+
campaign.medium << :phone
|
28
|
+
assert_stored campaign, :web, :print, :phone
|
29
|
+
assert_equal 1, campaign.medium.select { |value| value == :phone }.size
|
30
|
+
end
|
31
|
+
|
32
|
+
should "can assign new values at once to bitmask" do
|
33
|
+
campaign = CleanCampaign.new(:medium => [:web, :print])
|
34
|
+
assert_stored campaign, :web, :print
|
35
|
+
campaign.medium = [:phone, :email]
|
36
|
+
assert_stored campaign, :phone, :email
|
37
|
+
end
|
38
|
+
|
39
|
+
should "can add custom behavor to value proxies during bitmask definition" do
|
40
|
+
campaign = CleanCampaign.new(:medium => [:web, :print])
|
41
|
+
assert_raises NoMethodError do
|
42
|
+
campaign.medium.worked?
|
43
|
+
end
|
44
|
+
assert_nothing_raised do
|
45
|
+
campaign.misc.worked?
|
46
|
+
end
|
47
|
+
assert campaign.misc.worked?
|
48
|
+
end
|
49
|
+
|
50
|
+
should "cannot use unsupported values" do
|
51
|
+
assert_unsupported { CleanCampaign.new(:medium => [:web, :print, :this_will_fail]) }
|
52
|
+
campaign = CleanCampaign.new(:medium => :web)
|
53
|
+
assert_unsupported { campaign.medium << :this_will_fail_also }
|
54
|
+
assert_unsupported { campaign.medium = [:so_will_this] }
|
55
|
+
end
|
56
|
+
|
57
|
+
should "can determine bitmasks using convenience method" do
|
58
|
+
assert CleanCampaign.bitmask_for_medium(:web, :print)
|
59
|
+
assert_equal(
|
60
|
+
CleanCampaign.bitmasks[:medium][:web] | CleanCampaign.bitmasks[:medium][:print],
|
61
|
+
CleanCampaign.bitmask_for_medium(:web, :print)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
should "assert use of unknown value in convenience method will result in exception" do
|
66
|
+
assert_unsupported { CleanCampaign.bitmask_for_medium(:web, :and_this_isnt_valid) }
|
67
|
+
end
|
68
|
+
|
69
|
+
should "hash of values is with indifferent access" do
|
70
|
+
string_bit = nil
|
71
|
+
assert_nothing_raised do
|
72
|
+
assert (string_bit = CleanCampaign.bitmask_for_medium('web', 'print'))
|
73
|
+
end
|
74
|
+
assert_equal CleanCampaign.bitmask_for_medium(:web, :print), string_bit
|
75
|
+
end
|
76
|
+
|
77
|
+
should "save bitmask with non-standard attribute names" do
|
78
|
+
campaign = CleanCampaign.new(:Legacy => [:upper, :case])
|
79
|
+
assert_equal [:upper, :case], campaign.Legacy
|
80
|
+
end
|
81
|
+
|
82
|
+
should "ignore blanks fed as values" do
|
83
|
+
campaign = CleanCampaign.new(:medium => [:web, :print, ''])
|
84
|
+
assert_stored campaign, :web, :print
|
85
|
+
end
|
86
|
+
|
87
|
+
should "convert values passed as strings to symbols" do
|
88
|
+
campaign = CleanCampaign.new
|
89
|
+
campaign.medium << "web"
|
90
|
+
assert_equal [:web], campaign.medium
|
91
|
+
assert_equal true, campaign.medium?("web")
|
92
|
+
end
|
93
|
+
|
94
|
+
context "checking" do
|
95
|
+
|
96
|
+
setup { @campaign = CleanCampaign.new(:medium => [:web, :print]) }
|
97
|
+
|
98
|
+
context "for a single value" do
|
99
|
+
|
100
|
+
should "be supported by an attribute_for_value convenience method" do
|
101
|
+
assert @campaign.medium_for_web?
|
102
|
+
assert @campaign.medium_for_print?
|
103
|
+
assert !@campaign.medium_for_email?
|
104
|
+
end
|
105
|
+
|
106
|
+
should "be supported by the simple predicate method" do
|
107
|
+
assert @campaign.medium?(:web)
|
108
|
+
assert @campaign.medium?(:print)
|
109
|
+
assert !@campaign.medium?(:email)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
context "for multiple values" do
|
115
|
+
|
116
|
+
should "be supported by the simple predicate method" do
|
117
|
+
assert @campaign.medium?(:web, :print)
|
118
|
+
assert !@campaign.medium?(:web, :email)
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
should "can check if at least one value is set" do
|
126
|
+
campaign = CleanCampaign.new(:medium => [:web, :print])
|
127
|
+
|
128
|
+
assert campaign.medium?
|
129
|
+
|
130
|
+
campaign = CleanCampaign.new
|
131
|
+
|
132
|
+
assert !campaign.medium?
|
133
|
+
end
|
134
|
+
|
135
|
+
#######
|
136
|
+
private
|
137
|
+
#######
|
138
|
+
|
139
|
+
def assert_unsupported(&block)
|
140
|
+
assert_raises(ArgumentError, &block)
|
141
|
+
end
|
142
|
+
|
143
|
+
def assert_stored(record, *values)
|
144
|
+
values.each do |value|
|
145
|
+
assert record.medium.any? { |v| v.to_s == value.to_s }, "Values #{record.medium.inspect} does not include #{value.inspect}"
|
146
|
+
end
|
147
|
+
full_mask = values.inject(0) do |mask, value|
|
148
|
+
mask | CleanCampaign.bitmasks[:medium][value]
|
149
|
+
end
|
150
|
+
assert_equal full_mask, record.medium.to_i
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
begin
|
5
|
+
require 'redgreen'
|
6
|
+
rescue LoadError
|
7
|
+
end
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
10
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
11
|
+
require 'bitmask-attribute'
|
12
|
+
require File.dirname(__FILE__) + '/../rails/init'
|
13
|
+
|
14
|
+
# Pseudo model for testing purposes
|
15
|
+
class CleanCampaign
|
16
|
+
include BitmaskAttribute
|
17
|
+
bitmask :medium, :as => [:web, :print, :email, :phone]
|
18
|
+
bitmask :misc, :as => %w(some useless values) do
|
19
|
+
def worked?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
bitmask :Legacy, :as => [:upper, :case]
|
24
|
+
end
|
25
|
+
|
26
|
+
class Test::Unit::TestCase
|
27
|
+
|
28
|
+
def assert_unsupported(&block)
|
29
|
+
assert_raises(ArgumentError, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def assert_stored(record, *values)
|
33
|
+
values.each do |value|
|
34
|
+
assert record.medium.any? { |v| v.to_s == value.to_s }, "Values #{record.medium.inspect} does not include #{value.inspect}"
|
35
|
+
end
|
36
|
+
full_mask = values.inject(0) do |mask, value|
|
37
|
+
mask | Campaign.bitmasks[:medium][value]
|
38
|
+
end
|
39
|
+
assert_equal full_mask, record.medium.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/core_ext/object'
|
7
|
+
require 'active_record'
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'redgreen'
|
11
|
+
require 'active_support/hash_with_indifferent_access'
|
12
|
+
rescue LoadError
|
13
|
+
end
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
16
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
17
|
+
require 'bitmask-attribute'
|
18
|
+
require File.dirname(__FILE__) + '/../rails/init'
|
19
|
+
|
20
|
+
# ActiveRecord::Base.logger = Logger.new(STDOUT)
|
21
|
+
|
22
|
+
ActiveRecord::Base.establish_connection(
|
23
|
+
:adapter => 'sqlite3',
|
24
|
+
:database => ':memory:'
|
25
|
+
)
|
26
|
+
|
27
|
+
ActiveRecord::Schema.define do
|
28
|
+
create_table :campaigns do |t|
|
29
|
+
t.integer :company_id
|
30
|
+
t.integer :medium, :misc, :Legacy
|
31
|
+
end
|
32
|
+
create_table :companies do |t|
|
33
|
+
t.string :name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Company < ActiveRecord::Base
|
38
|
+
has_many :campaigns
|
39
|
+
end
|
40
|
+
|
41
|
+
# Pseudo model for testing purposes
|
42
|
+
class Campaign < ActiveRecord::Base
|
43
|
+
belongs_to :company
|
44
|
+
bitmask :medium, :as => [:web, :print, :email, :phone]
|
45
|
+
bitmask :misc, :as => %w(some useless values) do
|
46
|
+
def worked?
|
47
|
+
true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
bitmask :Legacy, :as => [:upper, :case]
|
51
|
+
end
|
52
|
+
|
53
|
+
class Test::Unit::TestCase
|
54
|
+
|
55
|
+
def assert_unsupported(&block)
|
56
|
+
assert_raises(ArgumentError, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def assert_stored(record, *values)
|
60
|
+
values.each do |value|
|
61
|
+
assert record.medium.any? { |v| v.to_s == value.to_s }, "Values #{record.medium.inspect} does not include #{value.inspect}"
|
62
|
+
end
|
63
|
+
full_mask = values.inject(0) do |mask, value|
|
64
|
+
mask | Campaign.bitmasks[:medium][value]
|
65
|
+
end
|
66
|
+
assert_equal full_mask, record.medium.to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|