has_bitfield 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 +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +60 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/lib/has_bitfield.rb +137 -0
- data/test/helper.rb +9 -0
- data/test/test_has_bitfield.rb +171 -0
- metadata +65 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Hugo Duncan
|
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.rdoc
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
= has_bitfield
|
2
|
+
|
3
|
+
A rails gem for easy bitfield support.
|
4
|
+
|
5
|
+
Has support form multiple bitfield columns. See http://github.com/aka47/has_flags for a gem with a different feature set.
|
6
|
+
|
7
|
+
== Install
|
8
|
+
|
9
|
+
gem install has_bitfield
|
10
|
+
|
11
|
+
== Usage
|
12
|
+
class MyClass << ActiveRecord::Base
|
13
|
+
has_bitfield :column_name,
|
14
|
+
1 => :flag1,
|
15
|
+
2 => :flag2,
|
16
|
+
3 => :flag3
|
17
|
+
end
|
18
|
+
|
19
|
+
== Attribute Accessors
|
20
|
+
readers: name, name?
|
21
|
+
writer: name=(v)
|
22
|
+
where 'name' is the name of the bit field. the (v) parameter can be true/false, "true"/"false", 0/1, 'yes'/'no',
|
23
|
+
or :yes/:no. specifically, the following (v) inputs evaluate to true:
|
24
|
+
[ true, 'true', 'yes', :yes, 'ok', :ok, 1, '1' ]
|
25
|
+
all others, including nil evaluate false.
|
26
|
+
groups_name = [Array of flags] , not the plural
|
27
|
+
groups_name << name
|
28
|
+
groups_name
|
29
|
+
group_ids
|
30
|
+
group_ids = [Array of flag-ids]
|
31
|
+
|
32
|
+
|
33
|
+
The database table must include the column 'column_name' as an integer.
|
34
|
+
|
35
|
+
Defaults are set in per column 'after_initialized' callbacks. If your model needs to use this
|
36
|
+
callback, define it in the class and invoke: initialize_column_name.
|
37
|
+
|
38
|
+
class MyClass << ActiveRecord::Base
|
39
|
+
has_bitfield :my_flags,
|
40
|
+
1 => {:flag1 => { :default => false }},
|
41
|
+
2 => :flag2,
|
42
|
+
3 => :flag3
|
43
|
+
|
44
|
+
after_initialize :initialize_my_flags
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
== Note on Patches/Pull Requests
|
49
|
+
|
50
|
+
* Fork the project.
|
51
|
+
* Make your feature addition or bug fix.
|
52
|
+
* Add tests for it. This is important so I don't break it in a
|
53
|
+
future version unintentionally.
|
54
|
+
* Commit, do not mess with rakefile, version, or history.
|
55
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
56
|
+
* Send me a pull request. Bonus points for topic branches.
|
57
|
+
|
58
|
+
== Copyright
|
59
|
+
|
60
|
+
Copyright (c) 2009 Hugo Duncan. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "has_bitfield"
|
8
|
+
gem.summary = %Q{A rails gem for easy bitfield support.}
|
9
|
+
gem.description = %Q{Enable bitfield properties in ActiveRecord models.}
|
10
|
+
gem.email = "hugo_duncan@yahoo.com"
|
11
|
+
gem.homepage = "http://github.com/hugoduncan/has_bitfield"
|
12
|
+
gem.authors = ["Hugo Duncan"]
|
13
|
+
end
|
14
|
+
Jeweler::GemcutterTasks.new
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rake/testtask'
|
20
|
+
Rake::TestTask.new(:test) do |test|
|
21
|
+
test.libs << 'lib' << 'test'
|
22
|
+
test.pattern = 'test/**/test_*.rb'
|
23
|
+
test.verbose = true
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'rcov/rcovtask'
|
28
|
+
Rcov::RcovTask.new do |test|
|
29
|
+
test.libs << 'test'
|
30
|
+
test.pattern = 'test/**/test_*.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
rescue LoadError
|
34
|
+
task :rcov do
|
35
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
task :test => :check_dependencies
|
40
|
+
|
41
|
+
task :default => :test
|
42
|
+
|
43
|
+
require 'rake/rdoctask'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
46
|
+
|
47
|
+
rdoc.rdoc_dir = 'rdoc'
|
48
|
+
rdoc.title = "has_bitfield #{version}"
|
49
|
+
rdoc.rdoc_files.include('README*')
|
50
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/has_bitfield.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
module HasBitfield
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
FLAG_VALUE_OPTIONS = [:default] unless defined? FLAG_VALUE_OPTIONS
|
8
|
+
FLAG_OPTIONS = [:as,:name] unless defined? FLAG_OPTIONS
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def has_bitfield(name, flags_hash)
|
12
|
+
@has_bitfield ||= {}
|
13
|
+
values = {}
|
14
|
+
bits = {}
|
15
|
+
defaults = {}
|
16
|
+
bit_names = {}
|
17
|
+
field = flags_hash.delete(:as) || "#{name}_flags".to_sym
|
18
|
+
@has_bitfield[name] = {
|
19
|
+
:as => field,
|
20
|
+
:values => values,
|
21
|
+
:bit_names => bit_names,
|
22
|
+
:defaults => defaults
|
23
|
+
}
|
24
|
+
|
25
|
+
default_mask = 0
|
26
|
+
|
27
|
+
flags_hash.each do |key, flag|
|
28
|
+
flag_name, options = hash_for_flag(flag)
|
29
|
+
raise ArgumentError, "has_bitfield: keys should be positive integers less than or equal to 32.
|
30
|
+
'#{key}' is invalid as a key for '#{flag_name}'." unless is_valid_bit?(key)
|
31
|
+
|
32
|
+
value = 2**(key - 1)
|
33
|
+
values[flag] = value
|
34
|
+
bit_names[key] = flag
|
35
|
+
default = options.has_key?(:default) ? options[:default] : false;
|
36
|
+
defaults[key] = default
|
37
|
+
default_mask = default_mask | value if default
|
38
|
+
methods_for_flag(flag_name, value, field)
|
39
|
+
end
|
40
|
+
|
41
|
+
method_for_name(name, field)
|
42
|
+
method_for_initialize(name, default_mask, field)
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def methods_for_flag(flag, value, field)
|
48
|
+
class_eval <<-EVAL
|
49
|
+
def #{flag}?
|
50
|
+
flag_set?(:#{field}, #{value})
|
51
|
+
end
|
52
|
+
|
53
|
+
def #{flag}
|
54
|
+
flag_set?(:#{field}, #{value})
|
55
|
+
end
|
56
|
+
|
57
|
+
def #{flag}=(value)
|
58
|
+
if value.is_a?(String)
|
59
|
+
value = Integer(value)
|
60
|
+
value = (value!=0)
|
61
|
+
end
|
62
|
+
value ? set_flag(:#{field}, #{value}) : clear_flag(:#{field}, #{value})
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.#{flag}_condition
|
66
|
+
condition_for_flag(:#{field}, #{value}, true)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.not_#{flag}_condition
|
70
|
+
condition_for_flag(:#{field}, #{value}, false)
|
71
|
+
end
|
72
|
+
EVAL
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_for_initialize(name, mask, field)
|
76
|
+
class_eval <<-EVAL
|
77
|
+
def initialize_#{name}
|
78
|
+
self[:#{field}] = #{mask}
|
79
|
+
end
|
80
|
+
EVAL
|
81
|
+
end
|
82
|
+
|
83
|
+
def method_for_name(name, field)
|
84
|
+
class_eval <<-EVAL
|
85
|
+
def #{name}
|
86
|
+
self[:#{field}]
|
87
|
+
end
|
88
|
+
EVAL
|
89
|
+
end
|
90
|
+
|
91
|
+
def flag_metadata
|
92
|
+
@has_bitfield
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_flag?(flag)
|
96
|
+
@has_bitfield.any? { |name, meta| meta.values.keys.include?(flag) }
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def condition_for_flag(field, value, enabled)
|
101
|
+
"(#{table_name}.#{field} & #{value} = #{enabled ? '1': '0'})"
|
102
|
+
end
|
103
|
+
|
104
|
+
def is_valid_bit?(key)
|
105
|
+
key > 0 && key == key.to_i && key < 33
|
106
|
+
end
|
107
|
+
|
108
|
+
def hash_for_flag(value)
|
109
|
+
if value.is_a?(Symbol)
|
110
|
+
[ value, {} ]
|
111
|
+
else
|
112
|
+
if value.size != 1
|
113
|
+
raise ArgumentError("invalid flag specification for #{value.inspect}")
|
114
|
+
end
|
115
|
+
[ value.keys[0], value.values[0] ]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end # module
|
119
|
+
|
120
|
+
private
|
121
|
+
def set_flag(field, value)
|
122
|
+
self[field] = self[field] | value
|
123
|
+
end
|
124
|
+
|
125
|
+
def clear_flag(field, value)
|
126
|
+
self[field] = self[field] & ~value
|
127
|
+
end
|
128
|
+
|
129
|
+
def flag_set?(field, value)
|
130
|
+
self[field] & value != 0
|
131
|
+
end
|
132
|
+
|
133
|
+
def flag_clear?(field, value)
|
134
|
+
self[field] & value == 0
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
require 'active_support/test_case'
|
4
|
+
|
5
|
+
# ActiveRecord proxies
|
6
|
+
module Proxy
|
7
|
+
class Column
|
8
|
+
def name
|
9
|
+
'flags'
|
10
|
+
end
|
11
|
+
def type
|
12
|
+
:integer
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Base < Hash
|
17
|
+
def self.columns
|
18
|
+
[Column.new]
|
19
|
+
end
|
20
|
+
|
21
|
+
def flags=(value)
|
22
|
+
self[:flags] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def flags
|
26
|
+
self[:flags]
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Foo < Proxy::Base
|
33
|
+
include ::HasBitfield
|
34
|
+
|
35
|
+
def self.table_name
|
36
|
+
"foos"
|
37
|
+
end
|
38
|
+
|
39
|
+
has_bitfield(:my_flags,
|
40
|
+
:as => :flags,
|
41
|
+
1 => :active,
|
42
|
+
2 => :suspended,
|
43
|
+
3 => { :another => { :default => true } } )
|
44
|
+
end
|
45
|
+
|
46
|
+
class HasBitfieldTest < ActiveSupport::TestCase
|
47
|
+
|
48
|
+
test "should raise an exception when a negative bit is specified" do
|
49
|
+
assert_raises ArgumentError do
|
50
|
+
eval(<<-EOF
|
51
|
+
class Invalid < Proxy::Base
|
52
|
+
include ::HasBitfield
|
53
|
+
has_bitfield({ -1 => :error })
|
54
|
+
end
|
55
|
+
EOF
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
test "should raise an exception when a zero bit is specified" do
|
61
|
+
assert_raises ArgumentError do
|
62
|
+
eval(<<-EOF
|
63
|
+
class Invalid < Proxy::Base
|
64
|
+
include ::HasBitfield
|
65
|
+
has_bitfield({ 0 => :error })
|
66
|
+
end
|
67
|
+
EOF
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
test "should raise an exception when a bit above 32 is specified" do
|
73
|
+
assert_raises ArgumentError do
|
74
|
+
eval(<<-EOF
|
75
|
+
class Invalid < Proxy::Base
|
76
|
+
include ::HasBitfield
|
77
|
+
has_bitfield({ 33 => :error })
|
78
|
+
end
|
79
|
+
EOF
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def setup
|
85
|
+
@foo = Foo.new
|
86
|
+
end
|
87
|
+
|
88
|
+
test "test class should have flags field" do
|
89
|
+
@foo.flags = 1
|
90
|
+
assert_equal 1, @foo.flags
|
91
|
+
assert_equal 1, @foo[:flags]
|
92
|
+
|
93
|
+
@foo[:flags] = 2
|
94
|
+
assert_equal 2, @foo.flags
|
95
|
+
assert_equal 2, @foo[:flags]
|
96
|
+
end
|
97
|
+
|
98
|
+
test "should have my_flags reader" do
|
99
|
+
@foo.flags = 1
|
100
|
+
assert_equal 1, @foo.my_flags
|
101
|
+
|
102
|
+
@foo[:flags] = 2
|
103
|
+
assert_equal 2, @foo.my_flags
|
104
|
+
end
|
105
|
+
|
106
|
+
test "should return the initialized flag field" do
|
107
|
+
@foo.initialize_my_flags
|
108
|
+
assert_equal 4, @foo.my_flags
|
109
|
+
end
|
110
|
+
|
111
|
+
test "should correctly define predicate" do
|
112
|
+
@foo[:flags] = 4
|
113
|
+
assert !@foo.active?
|
114
|
+
assert !@foo.suspended?
|
115
|
+
assert @foo.another?
|
116
|
+
end
|
117
|
+
|
118
|
+
test "should correctly define reader" do
|
119
|
+
@foo[:flags] = 4
|
120
|
+
assert !@foo.active
|
121
|
+
assert !@foo.suspended
|
122
|
+
assert @foo.another
|
123
|
+
end
|
124
|
+
|
125
|
+
test "should correctly define assignment" do
|
126
|
+
@foo[:flags] = 4
|
127
|
+
@foo.active = true
|
128
|
+
assert @foo.active?
|
129
|
+
assert !@foo.suspended?
|
130
|
+
assert @foo.another?
|
131
|
+
|
132
|
+
@foo.active = false
|
133
|
+
assert !@foo.active?
|
134
|
+
assert !@foo.suspended?
|
135
|
+
assert @foo.another?
|
136
|
+
end
|
137
|
+
|
138
|
+
test "should correctly assign from string" do
|
139
|
+
@foo[:flags] = 4
|
140
|
+
@foo.active="1"
|
141
|
+
assert @foo.active
|
142
|
+
@foo.active="0"
|
143
|
+
assert !@foo.active
|
144
|
+
end
|
145
|
+
|
146
|
+
test "should define condition" do
|
147
|
+
assert_equal "(foos.flags & 1 = 1)", Foo.active_condition
|
148
|
+
end
|
149
|
+
|
150
|
+
test "should define negated condition" do
|
151
|
+
assert_equal "(foos.flags & 1 = 0)", Foo.not_active_condition
|
152
|
+
end
|
153
|
+
|
154
|
+
test "should correctly assign current value" do
|
155
|
+
@foo[:flags] = 4
|
156
|
+
@foo.active = true
|
157
|
+
2.times do
|
158
|
+
@foo.active = false
|
159
|
+
assert !@foo.active?
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
test "should have an initial value" do
|
164
|
+
@foo.initialize_my_flags
|
165
|
+
assert_equal false, @foo.active?
|
166
|
+
assert_equal false, @foo.suspended?
|
167
|
+
assert_equal true, @foo.another?
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_bitfield
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hugo Duncan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-16 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Enable bitfield properties in ActiveRecord models.
|
17
|
+
email: hugo_duncan@yahoo.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- .document
|
27
|
+
- .gitignore
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
- Rakefile
|
31
|
+
- VERSION
|
32
|
+
- lib/has_bitfield.rb
|
33
|
+
- test/helper.rb
|
34
|
+
- test/test_has_bitfield.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://github.com/hugoduncan/has_bitfield
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --charset=UTF-8
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.3.5
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: A rails gem for easy bitfield support.
|
63
|
+
test_files:
|
64
|
+
- test/helper.rb
|
65
|
+
- test/test_has_bitfield.rb
|