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 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