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