bitmask_attributes 0.2.4 → 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.
data/.gitignore CHANGED
@@ -6,4 +6,5 @@ pkg
6
6
  \#*
7
7
  .#*
8
8
  *.gem
9
- /nbproject
9
+ /nbproject
10
+ .idea/
@@ -0,0 +1,4 @@
1
+ script: 'bundle exec rake test'
2
+ language: ruby
3
+ rvm:
4
+ - 1.9.3
@@ -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
- * add netbeans project files to .gitignore
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
- * Merge pull request #3 from IvanBuiko/master
10
- * Added changeling tasks
11
- * Created docs
62
+ * Added changeling tasks for help with gem releases
63
+ * Created docs using sdoc
64
+
12
65
 
13
66
 
14
67
 
data/Gemfile CHANGED
@@ -2,12 +2,8 @@ source "http://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- if RUBY_VERSION < '1.9'
6
- gem "ruby-debug", ">= 0.10.3"
7
- end
8
-
9
5
  gem 'sdoc'
10
6
  gem 'rake'
11
7
  gem 'shoulda'
12
- gem 'sqlite3', '>=1.3.4'
13
- gem 'turn'
8
+ gem 'sqlite3', '>=1.3.5'
9
+ gem 'turn'
@@ -1,40 +1,40 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bitmask_attributes (0.2.3)
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.1.3)
11
- activesupport (= 3.1.3)
10
+ activemodel (3.2.2)
11
+ activesupport (= 3.2.2)
12
12
  builder (~> 3.0.0)
13
- i18n (~> 0.6)
14
- activerecord (3.1.3)
15
- activemodel (= 3.1.3)
16
- activesupport (= 3.1.3)
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.1.3)
18
+ activesupport (3.2.2)
19
+ i18n (~> 0.6)
20
20
  multi_json (~> 1.0)
21
- ansi (1.4.1)
22
- arel (2.2.1)
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.1)
26
- multi_json (1.0.3)
25
+ json (1.6.5)
26
+ multi_json (1.1.0)
27
27
  rake (0.9.2.2)
28
- rdoc (3.11)
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.4)
34
+ sqlite3 (1.3.5)
35
35
  turn (0.8.3)
36
36
  ansi
37
- tzinfo (0.3.31)
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.4)
47
+ sqlite3 (>= 1.3.5)
48
48
  turn
@@ -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 a specific attribute.
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
- Note that "without_" only supports a single attribute argument, and the "no_" method does not support arguments.
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
 
@@ -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, &extension)
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
- raise ArgumentError, "`#{attribute}' is not an attribute of `#{model}'"
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(&:blank?))
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
- unless (bit = bitmasks[:#{attribute}][value])
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 OR #{attribute} IS NOT NULL')
118
+ where('#{attribute} > 0')
113
119
  else
114
120
  sets = values.map do |value|
115
- mask = #{model}.bitmask_for_#{attribute}(value)
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 { |value|
123
- if value
124
- mask = #{model}.bitmask_for_#{attribute}(value)
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} IS NULL OR #{attribute} = 0")
128
- end
129
- }
130
-
131
- scope :no_#{attribute}, where("#{attribute} = 0 OR #{attribute} IS NULL")
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 OR #{attribute} IS NOT NULL')
150
+ where('#{attribute} > 0')
137
151
  else
138
- sets = values.map do |value|
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', #{model}.bitmask_for_#{attribute}(:#{value}))
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,3 +1,3 @@
1
1
  module BitmaskAttributes
2
- VERSION = "0.2.4".freeze
2
+ VERSION = "0.3.0".freeze
3
3
  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
- should "can assign multiple values to bitmask" do
20
- assert_stored Campaign.new(:medium => [:web, :print]), :web, :print
21
- end
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
- should "can add single value to bitmask" do
24
- campaign = Campaign.new(:medium => [:web, :print])
25
- assert_stored campaign, :web, :print
26
- campaign.medium << :phone
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
- should "ignores duplicate values added to bitmask" do
31
- campaign = Campaign.new(:medium => [:web, :print])
32
- assert_stored campaign, :web, :print
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
- should "can assign new values at once to bitmask" do
44
- campaign = Campaign.new(:medium => [:web, :print])
45
- assert_stored campaign, :web, :print
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
- should "can save bitmask to db and retrieve values transparently" do
51
- campaign = Campaign.new(:medium => [:web, :print])
52
- assert_stored campaign, :web, :print
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
- should "can add custom behavor to value proxies during bitmask definition" do
58
- campaign = Campaign.new(:medium => [:web, :print])
59
- assert_raises NoMethodError do
60
- campaign.medium.worked?
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
- assert_nothing_raised do
63
- campaign.misc.worked?
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
- should "cannot use unsupported values" do
69
- assert_unsupported { Campaign.new(:medium => [:web, :print, :this_will_fail]) }
70
- campaign = Campaign.new(:medium => :web)
71
- assert_unsupported { campaign.medium << :this_will_fail_also }
72
- assert_unsupported { campaign.medium = [:so_will_this] }
73
- end
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
- should "can determine bitmasks using convenience method" do
76
- assert Campaign.bitmask_for_medium(:web, :print)
77
- assert_equal(
78
- Campaign.bitmasks[:medium][:web] | Campaign.bitmasks[:medium][:print],
79
- Campaign.bitmask_for_medium(:web, :print)
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
- should "hash of values is with indifferent access" do
88
- string_bit = nil
89
- assert_nothing_raised do
90
- assert (string_bit = Campaign.bitmask_for_medium('web', 'print'))
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
- should "save bitmask with non-standard attribute names" do
96
- campaign = Campaign.new(:Legacy => [:upper, :case])
97
- assert campaign.save
98
- assert_equal [:upper, :case], Campaign.find(campaign.id).Legacy
99
- end
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
- should "ignore blanks fed as values" do
102
- campaign = Campaign.new(:medium => [:web, :print, ''])
103
- assert_stored campaign, :web, :print
104
- end
105
-
106
- context "checking" do
107
- setup { @campaign = Campaign.new(:medium => [:web, :print]) }
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
- context "for a single value" do
110
- should "be supported by an attribute_for_value convenience method" do
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
- context "for multiple values" do
124
- should "be supported by the simple predicate method" do
125
- assert @campaign.medium?(:web, :print)
126
- assert !@campaign.medium?(:web, :email)
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
- context "named scopes" do
132
- setup do
133
- @company = Company.create(:name => "Test Co, Intl.")
134
- @campaign1 = @company.campaigns.create :medium => [:web, :print]
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 "support retrieval by any value" do
140
- assert_equal [@campaign1, @campaign3], @company.campaigns.with_medium
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
- should "support retrieval by one matching value" do
144
- assert_equal [@campaign1], @company.campaigns.with_medium(:print)
145
- end
146
-
147
- should "support retrieval by any matching value (OR)" do
148
- assert_equal [@campaign1, @campaign3], @company.campaigns.with_any_medium(:print, :email)
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
- should "support retrieval by all matching values" do
152
- assert_equal [@campaign1], @company.campaigns.with_medium(:web, :print)
153
- assert_equal [@campaign3], @company.campaigns.with_medium(:web, :email)
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 "support retrieval for no values" do
157
- assert_equal [@campaign2], @company.campaigns.without_medium
158
- assert_equal [@campaign2], @company.campaigns.no_medium
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 "support retrieval without a specific value" do
162
- assert_equal [@campaign2, @campaign3], @company.campaigns.without_medium(:print)
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
- should "can check if at least one value is set" do
167
- campaign = Campaign.new(:medium => [:web, :print])
168
- assert campaign.medium?
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
- should "find by bitmask values" do
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
- should "find no values" do
190
- campaign = Campaign.create(:medium => [:web, :print])
191
- assert campaign.save
192
-
193
- assert_equal [], Campaign.no_medium
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
- private
240
+ campaign = @campaign_class.new(:allow_zero => :none)
241
+ assert campaign.save
242
+ assert_equal [],campaign.allow_zero
203
243
 
204
- def assert_unsupported(&block)
205
- assert_raises(ArgumentError, &block)
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
- def assert_stored(record, *values)
209
- values.each do |value|
210
- assert record.medium.any? { |v| v.to_s == value.to_s }, "Values #{record.medium.inspect} does not include #{value.inspect}"
253
+
254
+ private
255
+
256
+ def assert_unsupported(&block)
257
+ assert_raises(ArgumentError, &block)
211
258
  end
212
- full_mask = values.inject(0) do |mask, value|
213
- mask | Campaign.bitmasks[:medium][value]
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
@@ -1,26 +1,50 @@
1
1
  ActiveRecord::Schema.define do
2
- create_table :campaigns do |t|
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 :companies do |t|
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 Company < ActiveRecord::Base
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
- # Pseudo model for testing purposes
17
- class Campaign < ActiveRecord::Base
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.2.4
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: 2011-11-23 00:00:00.000000000 Z
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: &70229913138200 !ruby/object:Gem::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: *70229913138200
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.10
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
@@ -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