has_flags 0.0.1
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/README +51 -0
- data/lib/has_flags.rb +214 -0
- data/rails/init.rb +1 -0
- data/tasks/has_flags_tasks.rake +4 -0
- data/test/has_flags_test.rb +67 -0
- metadata +69 -0
data/README
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
= has_flags.rb - Has Bit Flags Plugin
|
2
|
+
|
3
|
+
Copyright 2006, Rain City Software, Inc. License: Apache 2.0, Version 0.1.4 11-Aug-2006
|
4
|
+
|
5
|
+
== Overview
|
6
|
+
|
7
|
+
The has_flags plugin provides a simple way to define and access multiple
|
8
|
+
boolean flags that map to a single integer database column. The primary
|
9
|
+
objects for using flags are:
|
10
|
+
|
11
|
+
1) control and access the state of an object
|
12
|
+
2) to enable finding database rows with a particular state
|
13
|
+
3) to ease long term maintenance by providing a database definition independent way of extending states
|
14
|
+
4) to be able to adapt to legacy systems that use this approach
|
15
|
+
|
16
|
+
|
17
|
+
== Conventions:
|
18
|
+
|
19
|
+
*) Db Column Name: bit_flags
|
20
|
+
*) attribute writer (setter) accepts true, 'true', 'yes', :yes, 1, '1', 'ok' to evaluate true. all others are false.
|
21
|
+
|
22
|
+
== Sample Use:
|
23
|
+
|
24
|
+
class PayableInvoice << ActiveRecord::Base
|
25
|
+
has_flags [ :active, true, :ok_to_post, 7, :posted, :archived ]
|
26
|
+
end
|
27
|
+
|
28
|
+
=== instance attrs
|
29
|
+
|
30
|
+
invoice = Payable.new => new payable object created, uses 'after_initialize' to set defaults
|
31
|
+
invoice.active? => true
|
32
|
+
invoice.active => true
|
33
|
+
invoice.ok_to_post? => false
|
34
|
+
invoice.bit_flags => 1 # follows the 'active=true' configuration
|
35
|
+
|
36
|
+
invoice.active = v => true where v is in [ true, 'true', 1, '1', 'yes', 'YeS', :yes, :ok 'OK', "oK" ]
|
37
|
+
invoice.active = v => false where v.to_s.upcase is not in the list above
|
38
|
+
|
39
|
+
=== Class Methods
|
40
|
+
|
41
|
+
PayableInvoice.flags => { "active"=>1, "ok_to_post"=>128, "posted"=>256, "archived"=>512 }
|
42
|
+
PayableInvoice.mask(:ok_to_post, :posted) => 384 (0b0110000000)
|
43
|
+
PayableInvoice.mask(:active, :archived) => 512 (0x1000000000)
|
44
|
+
|
45
|
+
=== Finders
|
46
|
+
|
47
|
+
PayableInvoice.find_by_flags(:active, :ok_to_post) => returns a list of objects that are 'active' and 'ok to post'
|
48
|
+
PayableInvoice.find_by_flags(:not_active) => returns a list of objects that are not active
|
49
|
+
|
50
|
+
For other examples, see http://RainCityOnRails.com/has_flags
|
51
|
+
|
data/lib/has_flags.rb
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
################################################################################################
|
2
|
+
# darryl.west@RainCitySoftware.com
|
3
|
+
# Copyright 2006, Rain City Software, all rights reserved
|
4
|
+
# version 0.1.4
|
5
|
+
#
|
6
|
+
# License: Apache 2.0
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# class MyClass << ActiveRecord::Base
|
10
|
+
# has_flags [ :symbol, position, true/false, :symbol, ... ], [ options ]
|
11
|
+
# end
|
12
|
+
# Options:
|
13
|
+
# :column = 'bit_flags'
|
14
|
+
#
|
15
|
+
# Attribute Accessors:
|
16
|
+
# readers: name, name?
|
17
|
+
# writer: name=(v)
|
18
|
+
# where 'name' is the name of the bit field. the (v) parameter can be true/false, "true"/"false", 0/1, 'yes'/'no',
|
19
|
+
# or :yes/:no. specifically, the following (v) inputs evaluate to true:
|
20
|
+
# [ true, 'true', 'yes', :yes, 'ok', :ok, 1, '1' ]
|
21
|
+
# all others, including nil evaluate false.
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# Note: db table must include the column 'bit_flags' as an integer. Use the :column option to
|
25
|
+
# override with an alternate name
|
26
|
+
# Note: defaults are set in the 'after_initialized' callback. If your model needs to use this
|
27
|
+
# callback, define it in the class and invoke: init_flags. Here is an example:
|
28
|
+
#
|
29
|
+
# class MyClass << ActiveRecord::Base
|
30
|
+
# has_flags [ :symbol, position, true/false, :symbol, ... ], [ options ]
|
31
|
+
# def after_initialize
|
32
|
+
# init_flags
|
33
|
+
# ... do more stuff ...
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# Example Use:
|
38
|
+
# has_flags [ :active, true, :has_invoice, 3, :canceled, 8 ]
|
39
|
+
# this example creates nine accessor methods, active?, active, active=(v), has_invoice?, has_invoice,
|
40
|
+
# has_invoice=(v), canceled?, canceled and canceled=(v). the 'bit_flags' bit flag is updated to follow
|
41
|
+
# the setters, and when the class is initialized as new (not after a find), the default (active=true) is set.
|
42
|
+
#
|
43
|
+
# has_flags [ :active, true, :deleted 8 ], [ :column = 'status_flags' ]
|
44
|
+
# The has_flags method registers the Symbol objects as ? and =(v) accessors. it also takes care of
|
45
|
+
# updating the underlying data element (defaults to 'bit_flags') when the =(v) methods are invoked. So
|
46
|
+
# if MyClass uses has_flags, then you could do the following:
|
47
|
+
# myclass = MyClass.new
|
48
|
+
# active = false if active?
|
49
|
+
# has_invoice = false
|
50
|
+
# myclass.save
|
51
|
+
# This would change the myclass.bit_flags value, then send it to the database.
|
52
|
+
#
|
53
|
+
require 'active_record'
|
54
|
+
|
55
|
+
module RainCity
|
56
|
+
module Has #:nodoc:
|
57
|
+
module BitFlags #:nodoc:
|
58
|
+
|
59
|
+
# this hook is called by ActiveRecord::Base
|
60
|
+
def self.included(mod)
|
61
|
+
mod.extend(ClassMethods)
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# declare the class method "has_flags"
|
66
|
+
# create the instance methods based on bitflags and options
|
67
|
+
#
|
68
|
+
module ClassMethods
|
69
|
+
def has_flags(bitflags, options = { })
|
70
|
+
unless respond_to? :bitflag_attr
|
71
|
+
class_eval { cattr_accessor :bitflag_column, :bitflags, :default_mask }
|
72
|
+
# use config to lookup these values
|
73
|
+
self.bitflag_column, self.bitflags, self.default_mask = 'bit_flags', { }, 0
|
74
|
+
end
|
75
|
+
|
76
|
+
parse_options options
|
77
|
+
create_accessors bitflags
|
78
|
+
|
79
|
+
# now declare the class Singleton methods
|
80
|
+
class_eval { extend RainCity::Has::BitFlags::BitFlagMethods }
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def create_accessors(bitflags)
|
85
|
+
name = nil
|
86
|
+
position = 0
|
87
|
+
default = false
|
88
|
+
column = self.bitflag_column
|
89
|
+
bitflags.each do |v|
|
90
|
+
case v
|
91
|
+
when Symbol, String
|
92
|
+
if name
|
93
|
+
add_accessors(column, name, position, default)
|
94
|
+
|
95
|
+
default = false
|
96
|
+
position += 1
|
97
|
+
end
|
98
|
+
|
99
|
+
name = v.to_s
|
100
|
+
when Fixnum
|
101
|
+
position = v
|
102
|
+
when TrueClass, FalseClass
|
103
|
+
default = v
|
104
|
+
else
|
105
|
+
raise RuntimeError, "BitFlag: initialize error: " + bitflags.inspect
|
106
|
+
end
|
107
|
+
end
|
108
|
+
# now do the last one
|
109
|
+
add_accessors(column, name, position, default)
|
110
|
+
|
111
|
+
define_method( 'initialize_flags') do
|
112
|
+
if @new_record
|
113
|
+
self[column] = self.default_mask
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
if true
|
118
|
+
define_method( 'after_initialize' ) do
|
119
|
+
initialize_flags
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# add the ? and = accessors. add the after_initialize method unless it's optioned out (:after_init => false)
|
126
|
+
#
|
127
|
+
def add_accessors(column, name, position, default)
|
128
|
+
mask = (1 << position)
|
129
|
+
self.bitflags[name] = mask
|
130
|
+
if default
|
131
|
+
self.default_mask += mask
|
132
|
+
end
|
133
|
+
|
134
|
+
# now the instance accessors
|
135
|
+
# return true/false
|
136
|
+
define_method( (name + '?').to_sym ) do
|
137
|
+
bits = self[column] ||= 0
|
138
|
+
bits[position].eql? 1
|
139
|
+
end
|
140
|
+
|
141
|
+
# set with true/false
|
142
|
+
define_method( (name + '=').to_sym ) do |v|
|
143
|
+
bits = self[column] ||= 0
|
144
|
+
flag = [ "true", '1', 'yes', 'ok' ].include? v.to_s.downcase
|
145
|
+
self[column] = flag ? bits |= mask : bits &= ~mask
|
146
|
+
end
|
147
|
+
|
148
|
+
# make this easy for the form builders like 'check_box' to use standard accessors
|
149
|
+
define_method( name.to_sym ) do
|
150
|
+
bits = self[column] ||= 0
|
151
|
+
bits[position].eql? 1
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
# parse the options for known symbols. raise a Runtime if a symbol is unrecognized
|
157
|
+
def parse_options(options)
|
158
|
+
options.each do |option|
|
159
|
+
option.each_pair do |opt, val|
|
160
|
+
case opt
|
161
|
+
when :column
|
162
|
+
self.bitflag_column = val.to_s
|
163
|
+
when :group
|
164
|
+
raise RuntimeError, 'Groups not supported for alpha version...'
|
165
|
+
else
|
166
|
+
raise RuntimeError, "BitFlag: unrecognized option: " + opt.to_s
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
# static Singletons
|
175
|
+
module BitFlagMethods
|
176
|
+
#
|
177
|
+
# return the flags as a hash of labels and values, i.e., the symbols (as strings) and bit mask values
|
178
|
+
#
|
179
|
+
def flags
|
180
|
+
self.bitflags
|
181
|
+
end
|
182
|
+
|
183
|
+
#
|
184
|
+
# return the integer mask given the list of defined symbols.
|
185
|
+
#
|
186
|
+
def mask(*args)
|
187
|
+
flags = self.flags
|
188
|
+
|
189
|
+
args.inject(0) do |n, name|
|
190
|
+
n += flags[name.to_s]
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
#
|
195
|
+
# return the list of objects using the defined symbols as bit arguments
|
196
|
+
#
|
197
|
+
def find_by_flags(*args)
|
198
|
+
flags = self.flags
|
199
|
+
|
200
|
+
msk = args.inject(0) do |n, name|
|
201
|
+
n += flags[name.to_s]
|
202
|
+
end
|
203
|
+
|
204
|
+
s = sprintf "(%s & %i)=%i", self.bitflag_column, msk, msk
|
205
|
+
self.find :all, :conditions => [ s ]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
ActiveRecord::Base.class_eval do
|
213
|
+
include RainCity::Has::BitFlags
|
214
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'has_flags'
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'active_record'
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/has_flags")
|
4
|
+
|
5
|
+
#
|
6
|
+
# darryl.west@RainCitySoftware.com
|
7
|
+
#
|
8
|
+
# test all methods and combinations of 'has_flags'.
|
9
|
+
#
|
10
|
+
class HasFlagsTest < Test::Unit::TestCase
|
11
|
+
|
12
|
+
class ModelA < ActiveRecord::Base
|
13
|
+
has_flags [ :active, true, :deleted, 7 ]
|
14
|
+
attr_accessor :bit_flags
|
15
|
+
end
|
16
|
+
|
17
|
+
class ModelB < ActiveRecord::Base
|
18
|
+
has_flags [ :active, true, :banned, 4, :deleted, 8, :archived, 10 ]
|
19
|
+
attr_accessor :bit_flags
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_class_methods
|
23
|
+
assert_respond_to ModelA, 'flags', 'should understand flags method'
|
24
|
+
assert_respond_to ModelA, 'mask', 'should understand mask method'
|
25
|
+
assert_respond_to ModelA, 'bitflag_column'
|
26
|
+
assert_respond_to ModelA, 'find_by_flags'
|
27
|
+
|
28
|
+
a = ModelA.flags
|
29
|
+
b = ModelB.flags
|
30
|
+
|
31
|
+
assert_equal 2, a.size
|
32
|
+
assert a.include?( 'active' )
|
33
|
+
assert a.include?( 'deleted' )
|
34
|
+
assert !a.include?( 'banned' ) # verify no cross-blead between classes
|
35
|
+
assert !a.include?( 'archived' )
|
36
|
+
|
37
|
+
assert_equal 4, b.size
|
38
|
+
assert b.include?( 'active' )
|
39
|
+
assert b.include?( 'banned' )
|
40
|
+
assert b.include?( 'deleted' )
|
41
|
+
assert b.include?( 'archived' )
|
42
|
+
|
43
|
+
assert_equal 1, a['active']
|
44
|
+
assert_equal 1, ModelA.mask( :active )
|
45
|
+
assert_equal 128, ModelA.mask( :deleted )
|
46
|
+
assert_equal 129, ModelA.mask( :active, :deleted )
|
47
|
+
assert_equal 1, ModelB.mask( :active )
|
48
|
+
assert_equal 16, ModelB.mask( :banned )
|
49
|
+
assert_equal 256, ModelB.mask( :deleted )
|
50
|
+
assert_equal 1024, ModelB.mask( :archived )
|
51
|
+
assert_equal 1297, ModelB.mask( :active, :banned, :deleted, :archived )
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_instance_methods
|
55
|
+
# TODO figure out how to get a connection to the database, create model a & b tables in setup, drop in teardown
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_options
|
59
|
+
# test the good ones
|
60
|
+
ActiveRecord::Base.has_flags [ :active, true, :deleted, 7 ], [ :column => 'my_column_name' ]
|
61
|
+
|
62
|
+
# test the bad...
|
63
|
+
assert_raise RuntimeError do
|
64
|
+
ActiveRecord::Base.has_flags [ :active, true, :deleted, 7 ], [ :bad_option => false ]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_flags
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Darryl West
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-09 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email: darryl.west@RainCitySoftware.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README
|
29
|
+
files:
|
30
|
+
- lib/has_flags.rb
|
31
|
+
- rails/init.rb
|
32
|
+
- tasks/has_flags_tasks.rake
|
33
|
+
- test/has_flags_test.rb
|
34
|
+
- README
|
35
|
+
homepage: https://github.com/ashleym1972/has-flags
|
36
|
+
licenses: []
|
37
|
+
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
hash: 3
|
49
|
+
segments:
|
50
|
+
- 0
|
51
|
+
version: "0"
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.7.2
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Using BitFlags in ActiveRecord
|
68
|
+
test_files: []
|
69
|
+
|