has_flags 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :has_flags do
3
+ # # Task goes here
4
+ # end
@@ -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
+