bitmask_attributes 0.1.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/.rvmrc +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +37 -0
- data/LICENSE +20 -0
- data/README.md +129 -0
- data/Rakefile +16 -0
- data/VERSION +1 -0
- data/bitmask_attributes.gemspec +20 -0
- data/lib/bitmask_attributes.rb +26 -0
- data/lib/bitmask_attributes/definition.rb +122 -0
- data/lib/bitmask_attributes/value_proxy.rb +68 -0
- data/lib/bitmask_attributes/version.rb +3 -0
- data/test/bitmask_attributes_test.rb +205 -0
- data/test/support/helpers.rb +17 -0
- data/test/support/models.rb +26 -0
- data/test/test_helper.rb +21 -0
- metadata +86 -0
data/.document
ADDED
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2@bitmask_attributes --create --install
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
bitmask_attributes (0.1.0)
|
5
|
+
activerecord (~> 3.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (3.0.9)
|
11
|
+
activesupport (= 3.0.9)
|
12
|
+
builder (~> 2.1.2)
|
13
|
+
i18n (~> 0.5.0)
|
14
|
+
activerecord (3.0.9)
|
15
|
+
activemodel (= 3.0.9)
|
16
|
+
activesupport (= 3.0.9)
|
17
|
+
arel (~> 2.0.10)
|
18
|
+
tzinfo (~> 0.3.23)
|
19
|
+
activesupport (3.0.9)
|
20
|
+
ansi (1.3.0)
|
21
|
+
arel (2.0.10)
|
22
|
+
builder (2.1.2)
|
23
|
+
i18n (0.5.0)
|
24
|
+
shoulda (2.11.3)
|
25
|
+
sqlite3 (1.3.3)
|
26
|
+
turn (0.8.2)
|
27
|
+
ansi (>= 1.2.2)
|
28
|
+
tzinfo (0.3.29)
|
29
|
+
|
30
|
+
PLATFORMS
|
31
|
+
ruby
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
bitmask_attributes!
|
35
|
+
shoulda
|
36
|
+
sqlite3
|
37
|
+
turn
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007-2009 Bruce Williams & 2011 Joel Moss
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
BitmaskAttributes
|
2
|
+
=================
|
3
|
+
|
4
|
+
Transparent manipulation of bitmask attributes for ActiveRecord, based on
|
5
|
+
the bitmask-attribute gem, which has been dormant since 2009. This updated
|
6
|
+
gem work with Rails 3 and up (including Rails 3.1).
|
7
|
+
|
8
|
+
Installation
|
9
|
+
------------
|
10
|
+
|
11
|
+
The best way to install is with RubyGems:
|
12
|
+
|
13
|
+
$ [sudo] gem install bitmask_attributes
|
14
|
+
|
15
|
+
Or better still, just add it to your Gemfile:
|
16
|
+
|
17
|
+
gem 'bitmask_attributes'
|
18
|
+
|
19
|
+
Example
|
20
|
+
-------
|
21
|
+
|
22
|
+
Simply declare an existing integer column as a bitmask with its possible
|
23
|
+
values.
|
24
|
+
|
25
|
+
class User < ActiveRecord::Base
|
26
|
+
bitmask :roles, :as => [:writer, :publisher, :editor, :proofreader]
|
27
|
+
end
|
28
|
+
|
29
|
+
You can then modify the column using the declared values without resorting
|
30
|
+
to manual bitmasks.
|
31
|
+
|
32
|
+
user = User.create(:name => "Bruce", :roles => [:publisher, :editor])
|
33
|
+
user.roles
|
34
|
+
# => [:publisher, :editor]
|
35
|
+
user.roles << :writer
|
36
|
+
user.roles
|
37
|
+
# => [:publisher, :editor, :writer]
|
38
|
+
|
39
|
+
It's easy to find out if a record has a given value:
|
40
|
+
|
41
|
+
user.roles?(:editor)
|
42
|
+
# => true
|
43
|
+
|
44
|
+
You can check for multiple values (uses an `and` boolean):
|
45
|
+
|
46
|
+
user.roles?(:editor, :publisher)
|
47
|
+
# => true
|
48
|
+
user.roles?(:editor, :proofreader)
|
49
|
+
# => false
|
50
|
+
|
51
|
+
Or, just check if any values are present:
|
52
|
+
|
53
|
+
user.roles?
|
54
|
+
# => true
|
55
|
+
|
56
|
+
Named Scopes
|
57
|
+
------------
|
58
|
+
|
59
|
+
A couple useful named scopes are also generated when you use
|
60
|
+
`bitmask`:
|
61
|
+
|
62
|
+
User.with_roles
|
63
|
+
# => (all users with roles)
|
64
|
+
User.with_roles(:editor)
|
65
|
+
# => (all editors)
|
66
|
+
User.with_roles(:editor, :writer)
|
67
|
+
# => (all users who are BOTH editors and writers)
|
68
|
+
|
69
|
+
Later we'll support an `or` boolean; for now, do something like:
|
70
|
+
|
71
|
+
User.with_roles(:editor) + User.with_roles(:writer)
|
72
|
+
# => (all users who are EITHER editors and writers)
|
73
|
+
|
74
|
+
Find records without any bitmask set:
|
75
|
+
|
76
|
+
User.without_roles
|
77
|
+
# => (all users without a role)
|
78
|
+
|
79
|
+
Later we'll support finding records without a specific bitmask.
|
80
|
+
|
81
|
+
Adding Methods
|
82
|
+
--------------
|
83
|
+
|
84
|
+
You can add your own methods to the bitmasked attributes (similar to
|
85
|
+
named scopes):
|
86
|
+
|
87
|
+
bitmask :other_attribute, :as => [:value1, :value2] do
|
88
|
+
def worked?
|
89
|
+
true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
user = User.first
|
94
|
+
user.other_attribute.worked?
|
95
|
+
# => true
|
96
|
+
|
97
|
+
|
98
|
+
Warning: Modifying possible values
|
99
|
+
----------------------------------
|
100
|
+
|
101
|
+
IMPORTANT: Once you have data using a bitmask, don't change the order
|
102
|
+
of the values, remove any values, or insert any new values in the `:as`
|
103
|
+
array anywhere except at the end. You won't like the results.
|
104
|
+
|
105
|
+
Contributing
|
106
|
+
------------
|
107
|
+
|
108
|
+
1. Fork it.
|
109
|
+
2. Create a branch (`git checkout -b new-feature`)
|
110
|
+
3. Make your changes
|
111
|
+
4. Run the tests (`bundle install` then `bundle exec rake`)
|
112
|
+
5. Commit your changes (`git commit -am "Created new feature"`)
|
113
|
+
6. Push to the branch (`git push origin new-feature`)
|
114
|
+
7. Create a [Pull Request](http://help.github.com/pull-requests/) from your branch.
|
115
|
+
8. Promote it. Get others to drop in and +1 it.
|
116
|
+
|
117
|
+
Credits
|
118
|
+
-------
|
119
|
+
|
120
|
+
Thanks to [Bruce Williams](https://github.com/bruce) and the following contributors
|
121
|
+
of the bitmask-attribute plugin:
|
122
|
+
|
123
|
+
* [Jason L Perry](http://github.com/ambethia)
|
124
|
+
* [Nicolas Fouché](http://github.com/nfo)
|
125
|
+
|
126
|
+
Copyright
|
127
|
+
---------
|
128
|
+
|
129
|
+
Copyright (c) 2007-2009 Bruce Williams & 2011 Joel Moss. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "bundler"
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
require 'rake/testtask'
|
7
|
+
|
8
|
+
desc 'Default: run unit tests.'
|
9
|
+
task :default => :test
|
10
|
+
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.1.0
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "bitmask_attributes/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "bitmask_attributes"
|
7
|
+
gem.summary = %Q{Simple bitmask attribute support for ActiveRecord}
|
8
|
+
gem.description = %Q{Simple bitmask attribute support for ActiveRecord}
|
9
|
+
gem.email = "joel@developwithstyle.com"
|
10
|
+
gem.homepage = "http://github.com/joelmoss/bitmask_attributes"
|
11
|
+
gem.authors = ['Joel Moss']
|
12
|
+
|
13
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
gem.files = `git ls-files`.split("\n")
|
15
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
gem.require_paths = ['lib']
|
17
|
+
gem.version = BitmaskAttributes::VERSION
|
18
|
+
|
19
|
+
gem.add_dependency 'activerecord', '~> 3.0'
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'bitmask_attributes/definition'
|
2
|
+
require 'bitmask_attributes/value_proxy'
|
3
|
+
|
4
|
+
module BitmaskAttributes
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def bitmask(attribute, options={}, &extension)
|
9
|
+
unless options[:as] && options[:as].kind_of?(Array)
|
10
|
+
raise ArgumentError, "Must provide an Array :as option"
|
11
|
+
end
|
12
|
+
bitmask_definitions[attribute] = Definition.new(attribute, options[:as].to_a, &extension)
|
13
|
+
bitmask_definitions[attribute].install_on(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def bitmask_definitions
|
17
|
+
@bitmask_definitions ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def bitmasks
|
21
|
+
@bitmasks ||= {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
ActiveRecord::Base.send :include, BitmaskAttributes
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module BitmaskAttributes
|
2
|
+
class Definition
|
3
|
+
attr_reader :attribute, :values, :extension
|
4
|
+
|
5
|
+
def initialize(attribute, values=[], &extension)
|
6
|
+
@attribute = attribute
|
7
|
+
@values = values
|
8
|
+
@extension = extension
|
9
|
+
end
|
10
|
+
|
11
|
+
def install_on(model)
|
12
|
+
validate_for model
|
13
|
+
generate_bitmasks_on model
|
14
|
+
override model
|
15
|
+
create_convenience_class_method_on model
|
16
|
+
create_convenience_instance_methods_on model
|
17
|
+
create_scopes_on model
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def validate_for(model)
|
23
|
+
# The model cannot be validated if it is preloaded and the attribute/column is not in the
|
24
|
+
# database (the migration has not been run). This usually
|
25
|
+
# occurs in the 'test' and 'production' environments.
|
26
|
+
return if defined?(Rails) && Rails.configuration.cache_classes
|
27
|
+
|
28
|
+
unless model.columns.detect { |col| col.name == attribute.to_s }
|
29
|
+
raise ArgumentError, "`#{attribute}' is not an attribute of `#{model}'"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_bitmasks_on(model)
|
34
|
+
model.bitmasks[attribute] = HashWithIndifferentAccess.new.tap do |mapping|
|
35
|
+
values.each_with_index do |value, index|
|
36
|
+
mapping[value] = 0b1 << index
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def override(model)
|
42
|
+
override_getter_on(model)
|
43
|
+
override_setter_on(model)
|
44
|
+
end
|
45
|
+
|
46
|
+
def override_getter_on(model)
|
47
|
+
model.class_eval %(
|
48
|
+
def #{attribute}
|
49
|
+
@#{attribute} ||= BitmaskAttributes::ValueProxy.new(self, :#{attribute}, &self.class.bitmask_definitions[:#{attribute}].extension)
|
50
|
+
end
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def override_setter_on(model)
|
55
|
+
model.class_eval %(
|
56
|
+
def #{attribute}=(raw_value)
|
57
|
+
values = raw_value.kind_of?(Array) ? raw_value : [raw_value]
|
58
|
+
self.#{attribute}.replace(values.reject(&:blank?))
|
59
|
+
end
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_convenience_class_method_on(model)
|
64
|
+
model.class_eval %(
|
65
|
+
def self.bitmask_for_#{attribute}(*values)
|
66
|
+
values.inject(0) do |bitmask, value|
|
67
|
+
unless (bit = bitmasks[:#{attribute}][value])
|
68
|
+
raise ArgumentError, "Unsupported value for #{attribute}: \#{value.inspect}"
|
69
|
+
end
|
70
|
+
bitmask | bit
|
71
|
+
end
|
72
|
+
end
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_convenience_instance_methods_on(model)
|
77
|
+
values.each do |value|
|
78
|
+
model.class_eval %(
|
79
|
+
def #{attribute}_for_#{value}?
|
80
|
+
self.#{attribute}?(:#{value})
|
81
|
+
end
|
82
|
+
)
|
83
|
+
end
|
84
|
+
model.class_eval %(
|
85
|
+
def #{attribute}?(*values)
|
86
|
+
if !values.blank?
|
87
|
+
values.all? do |value|
|
88
|
+
self.#{attribute}.include?(value)
|
89
|
+
end
|
90
|
+
else
|
91
|
+
self.#{attribute}.present?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_scopes_on(model)
|
98
|
+
model.class_eval %(
|
99
|
+
scope :with_#{attribute},
|
100
|
+
proc { |*values|
|
101
|
+
if values.blank?
|
102
|
+
{:conditions => '#{attribute} > 0 OR #{attribute} IS NOT NULL'}
|
103
|
+
else
|
104
|
+
sets = values.map do |value|
|
105
|
+
mask = #{model}.bitmask_for_#{attribute}(value)
|
106
|
+
"#{attribute} & \#{mask} <> 0"
|
107
|
+
end
|
108
|
+
{:conditions => sets.join(' AND ')}
|
109
|
+
end
|
110
|
+
}
|
111
|
+
scope :without_#{attribute}, :conditions => "#{attribute} == 0 OR #{attribute} IS NULL"
|
112
|
+
scope :no_#{attribute}, :conditions => "#{attribute} == 0 OR #{attribute} IS NULL"
|
113
|
+
)
|
114
|
+
values.each do |value|
|
115
|
+
model.class_eval %(
|
116
|
+
scope :#{attribute}_for_#{value},
|
117
|
+
:conditions => ['#{attribute} & ? <> 0', #{model}.bitmask_for_#{attribute}(:#{value})]
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module BitmaskAttributes
|
2
|
+
class ValueProxy < Array
|
3
|
+
|
4
|
+
def initialize(record, attribute, &extension)
|
5
|
+
@record = record
|
6
|
+
@attribute = attribute
|
7
|
+
find_mapping
|
8
|
+
instance_eval(&extension) if extension
|
9
|
+
super(extract_values)
|
10
|
+
end
|
11
|
+
|
12
|
+
# =========================
|
13
|
+
# = OVERRIDE TO SERIALIZE =
|
14
|
+
# =========================
|
15
|
+
|
16
|
+
%w(push << delete replace reject! select!).each do |override|
|
17
|
+
class_eval(<<-EOEVAL)
|
18
|
+
def #{override}(*args)
|
19
|
+
(super).tap do
|
20
|
+
updated!
|
21
|
+
end
|
22
|
+
end
|
23
|
+
EOEVAL
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_i
|
27
|
+
inject(0) { |memo, value| memo | @mapping[value] }
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def validate!
|
33
|
+
each do |value|
|
34
|
+
if @mapping.key? value
|
35
|
+
true
|
36
|
+
else
|
37
|
+
raise ArgumentError, "Unsupported value for `#{@attribute}': #{value.inspect}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def updated!
|
43
|
+
validate!
|
44
|
+
uniq!
|
45
|
+
serialize!
|
46
|
+
end
|
47
|
+
|
48
|
+
def serialize!
|
49
|
+
@record.send(:write_attribute, @attribute, to_i)
|
50
|
+
end
|
51
|
+
|
52
|
+
def extract_values
|
53
|
+
stored = [@record.send(:read_attribute, @attribute) || 0, 0].max
|
54
|
+
@mapping.inject([]) do |values, (value, bitmask)|
|
55
|
+
values.tap do
|
56
|
+
values << value.to_sym if (stored & bitmask > 0)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_mapping
|
62
|
+
unless (@mapping = @record.class.bitmasks[@attribute])
|
63
|
+
raise ArgumentError, "Could not find mapping for bitmask attribute :#{@attribute}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
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 "can assign single value to bitmask" do
|
12
|
+
assert_stored Campaign.new(:medium => :web), :web
|
13
|
+
end
|
14
|
+
|
15
|
+
should "can assign multiple values to bitmask" do
|
16
|
+
assert_stored Campaign.new(:medium => [:web, :print]), :web, :print
|
17
|
+
end
|
18
|
+
|
19
|
+
should "can add single value to bitmask" do
|
20
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
21
|
+
assert_stored campaign, :web, :print
|
22
|
+
campaign.medium << :phone
|
23
|
+
assert_stored campaign, :web, :print, :phone
|
24
|
+
end
|
25
|
+
|
26
|
+
should "ignores duplicate values added to bitmask" do
|
27
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
28
|
+
assert_stored campaign, :web, :print
|
29
|
+
campaign.medium << :phone
|
30
|
+
assert_stored campaign, :web, :print, :phone
|
31
|
+
campaign.medium << :phone
|
32
|
+
assert_stored campaign, :web, :print, :phone
|
33
|
+
assert_equal 1, campaign.medium.select { |value| value == :phone }.size
|
34
|
+
end
|
35
|
+
|
36
|
+
should "can assign new values at once to bitmask" do
|
37
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
38
|
+
assert_stored campaign, :web, :print
|
39
|
+
campaign.medium = [:phone, :email]
|
40
|
+
assert_stored campaign, :phone, :email
|
41
|
+
end
|
42
|
+
|
43
|
+
should "can save bitmask to db and retrieve values transparently" do
|
44
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
45
|
+
assert_stored campaign, :web, :print
|
46
|
+
assert campaign.save
|
47
|
+
assert_stored Campaign.find(campaign.id), :web, :print
|
48
|
+
end
|
49
|
+
|
50
|
+
should "can add custom behavor to value proxies during bitmask definition" do
|
51
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
52
|
+
assert_raises NoMethodError do
|
53
|
+
campaign.medium.worked?
|
54
|
+
end
|
55
|
+
assert_nothing_raised do
|
56
|
+
campaign.misc.worked?
|
57
|
+
end
|
58
|
+
assert campaign.misc.worked?
|
59
|
+
end
|
60
|
+
|
61
|
+
should "cannot use unsupported values" do
|
62
|
+
assert_unsupported { Campaign.new(:medium => [:web, :print, :this_will_fail]) }
|
63
|
+
campaign = Campaign.new(:medium => :web)
|
64
|
+
assert_unsupported { campaign.medium << :this_will_fail_also }
|
65
|
+
assert_unsupported { campaign.medium = [:so_will_this] }
|
66
|
+
end
|
67
|
+
|
68
|
+
should "can determine bitmasks using convenience method" do
|
69
|
+
assert Campaign.bitmask_for_medium(:web, :print)
|
70
|
+
assert_equal(
|
71
|
+
Campaign.bitmasks[:medium][:web] | Campaign.bitmasks[:medium][:print],
|
72
|
+
Campaign.bitmask_for_medium(:web, :print)
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
should "assert use of unknown value in convenience method will result in exception" do
|
77
|
+
assert_unsupported { Campaign.bitmask_for_medium(:web, :and_this_isnt_valid) }
|
78
|
+
end
|
79
|
+
|
80
|
+
should "hash of values is with indifferent access" do
|
81
|
+
string_bit = nil
|
82
|
+
assert_nothing_raised do
|
83
|
+
assert (string_bit = Campaign.bitmask_for_medium('web', 'print'))
|
84
|
+
end
|
85
|
+
assert_equal Campaign.bitmask_for_medium(:web, :print), string_bit
|
86
|
+
end
|
87
|
+
|
88
|
+
should "save bitmask with non-standard attribute names" do
|
89
|
+
campaign = Campaign.new(:Legacy => [:upper, :case])
|
90
|
+
assert campaign.save
|
91
|
+
assert_equal [:upper, :case], Campaign.find(campaign.id).Legacy
|
92
|
+
end
|
93
|
+
|
94
|
+
should "ignore blanks fed as values" do
|
95
|
+
campaign = Campaign.new(:medium => [:web, :print, ''])
|
96
|
+
assert_stored campaign, :web, :print
|
97
|
+
end
|
98
|
+
|
99
|
+
context "checking" do
|
100
|
+
setup { @campaign = Campaign.new(:medium => [:web, :print]) }
|
101
|
+
|
102
|
+
context "for a single value" do
|
103
|
+
should "be supported by an attribute_for_value convenience method" do
|
104
|
+
assert @campaign.medium_for_web?
|
105
|
+
assert @campaign.medium_for_print?
|
106
|
+
assert !@campaign.medium_for_email?
|
107
|
+
end
|
108
|
+
|
109
|
+
should "be supported by the simple predicate method" do
|
110
|
+
assert @campaign.medium?(:web)
|
111
|
+
assert @campaign.medium?(:print)
|
112
|
+
assert !@campaign.medium?(:email)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "for multiple values" do
|
117
|
+
should "be supported by the simple predicate method" do
|
118
|
+
assert @campaign.medium?(:web, :print)
|
119
|
+
assert !@campaign.medium?(:web, :email)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "named scopes" do
|
125
|
+
setup do
|
126
|
+
@company = Company.create(:name => "Test Co, Intl.")
|
127
|
+
@campaign1 = @company.campaigns.create :medium => [:web, :print]
|
128
|
+
@campaign2 = @company.campaigns.create
|
129
|
+
@campaign3 = @company.campaigns.create :medium => [:web, :email]
|
130
|
+
end
|
131
|
+
|
132
|
+
should "support retrieval by any value" do
|
133
|
+
assert_equal [@campaign1, @campaign3], @company.campaigns.with_medium
|
134
|
+
end
|
135
|
+
|
136
|
+
should "support retrieval by one matching value" do
|
137
|
+
assert_equal [@campaign1], @company.campaigns.with_medium(:print)
|
138
|
+
end
|
139
|
+
|
140
|
+
should "support retrieval by all matching values" do
|
141
|
+
assert_equal [@campaign1], @company.campaigns.with_medium(:web, :print)
|
142
|
+
assert_equal [@campaign3], @company.campaigns.with_medium(:web, :email)
|
143
|
+
end
|
144
|
+
|
145
|
+
should "support retrieval for no values" do
|
146
|
+
assert_equal [@campaign2], @company.campaigns.without_medium
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
should "can check if at least one value is set" do
|
151
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
152
|
+
|
153
|
+
assert campaign.medium?
|
154
|
+
|
155
|
+
campaign = Campaign.new
|
156
|
+
|
157
|
+
assert !campaign.medium?
|
158
|
+
end
|
159
|
+
|
160
|
+
should "find by bitmask values" do
|
161
|
+
campaign = Campaign.new(:medium => [:web, :print])
|
162
|
+
assert campaign.save
|
163
|
+
|
164
|
+
assert_equal(
|
165
|
+
Campaign.find(:all, :conditions => ['medium & ? <> 0', Campaign.bitmask_for_medium(:print)]),
|
166
|
+
Campaign.medium_for_print
|
167
|
+
)
|
168
|
+
|
169
|
+
assert_equal Campaign.medium_for_print.first, Campaign.medium_for_print.medium_for_web.first
|
170
|
+
|
171
|
+
assert_equal [], Campaign.medium_for_email
|
172
|
+
assert_equal [], Campaign.medium_for_web.medium_for_email
|
173
|
+
end
|
174
|
+
|
175
|
+
should "find no values" do
|
176
|
+
campaign = Campaign.create(:medium => [:web, :print])
|
177
|
+
assert campaign.save
|
178
|
+
|
179
|
+
assert_equal [], Campaign.no_medium
|
180
|
+
|
181
|
+
campaign.medium = []
|
182
|
+
assert campaign.save
|
183
|
+
|
184
|
+
assert_equal [campaign], Campaign.no_medium
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def assert_unsupported(&block)
|
191
|
+
assert_raises(ArgumentError, &block)
|
192
|
+
end
|
193
|
+
|
194
|
+
def assert_stored(record, *values)
|
195
|
+
values.each do |value|
|
196
|
+
assert record.medium.any? { |v| v.to_s == value.to_s }, "Values #{record.medium.inspect} does not include #{value.inspect}"
|
197
|
+
end
|
198
|
+
full_mask = values.inject(0) do |mask, value|
|
199
|
+
mask | Campaign.bitmasks[:medium][value]
|
200
|
+
end
|
201
|
+
assert_equal full_mask, record.medium.to_i
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,17 @@
|
|
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
|
@@ -0,0 +1,26 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table :campaigns do |t|
|
3
|
+
t.integer :company_id
|
4
|
+
t.integer :medium, :misc, :Legacy
|
5
|
+
end
|
6
|
+
create_table :companies do |t|
|
7
|
+
t.string :name
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
class Company < ActiveRecord::Base
|
13
|
+
has_many :campaigns
|
14
|
+
end
|
15
|
+
|
16
|
+
# Pseudo model for testing purposes
|
17
|
+
class Campaign < ActiveRecord::Base
|
18
|
+
belongs_to :company
|
19
|
+
bitmask :medium, :as => [:web, :print, :email, :phone]
|
20
|
+
bitmask :misc, :as => %w(some useless values) do
|
21
|
+
def worked?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
bitmask :Legacy, :as => [:upper, :case]
|
26
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'test/unit'
|
5
|
+
begin; require 'turn'; rescue LoadError; end
|
6
|
+
require 'shoulda'
|
7
|
+
|
8
|
+
require 'active_record'
|
9
|
+
|
10
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
11
|
+
require 'bitmask_attributes'
|
12
|
+
|
13
|
+
|
14
|
+
ActiveRecord::Base.establish_connection(
|
15
|
+
:adapter => 'sqlite3',
|
16
|
+
:database => ':memory:'
|
17
|
+
)
|
18
|
+
|
19
|
+
|
20
|
+
# Load support files
|
21
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bitmask_attributes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joel Moss
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-07-04 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: activerecord
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "3.0"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
description: Simple bitmask attribute support for ActiveRecord
|
28
|
+
email: joel@developwithstyle.com
|
29
|
+
executables: []
|
30
|
+
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files: []
|
34
|
+
|
35
|
+
files:
|
36
|
+
- .document
|
37
|
+
- .gitignore
|
38
|
+
- .rvmrc
|
39
|
+
- Gemfile
|
40
|
+
- Gemfile.lock
|
41
|
+
- LICENSE
|
42
|
+
- README.md
|
43
|
+
- Rakefile
|
44
|
+
- VERSION
|
45
|
+
- bitmask_attributes.gemspec
|
46
|
+
- lib/bitmask_attributes.rb
|
47
|
+
- lib/bitmask_attributes/definition.rb
|
48
|
+
- lib/bitmask_attributes/value_proxy.rb
|
49
|
+
- lib/bitmask_attributes/version.rb
|
50
|
+
- test/bitmask_attributes_test.rb
|
51
|
+
- test/support/helpers.rb
|
52
|
+
- test/support/models.rb
|
53
|
+
- test/test_helper.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/joelmoss/bitmask_attributes
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.3.9.1
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: Simple bitmask attribute support for ActiveRecord
|
82
|
+
test_files:
|
83
|
+
- test/bitmask_attributes_test.rb
|
84
|
+
- test/support/helpers.rb
|
85
|
+
- test/support/models.rb
|
86
|
+
- test/test_helper.rb
|