multi_bit_field 0.0.1 → 0.0.2
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.markdown +54 -12
- data/lib/multi_bit_field/version.rb +1 -1
- data/lib/multi_bit_field.rb +132 -15
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +16 -0
- data/test/dummy/log/test.log +968 -0
- data/test/multi_bit_field_test.rb +109 -3
- metadata +14 -8
data/README.markdown
CHANGED
@@ -25,12 +25,12 @@ Say you have daily, weekly and monthly counters:
|
|
25
25
|
|
26
26
|
If this model is now sorted in ascending order, it'll sort first by day, then by week and then by month. You could also compare counters with a paired "limit" field.
|
27
27
|
|
28
|
-
|
28
|
+
These methods only require an integer attribute (any ORM will do.) Here's how we'd set this up:
|
29
29
|
|
30
30
|
```ruby
|
31
31
|
class User < ActiveRecord::Base
|
32
|
-
has_bit_field :counter, :
|
33
|
-
has_bit_field :limit, :
|
32
|
+
has_bit_field :counter, :daily_count => 0..4, :weekly_count => 5..9, :monthly_count => 10..14
|
33
|
+
has_bit_field :limit, :daily_limit => 0..4, :weekly_limit => 5..9, :monthly_limit => 10..14
|
34
34
|
end
|
35
35
|
```
|
36
36
|
|
@@ -39,22 +39,66 @@ this provides the following methods:
|
|
39
39
|
```ruby
|
40
40
|
person = Person.new :daily => 3, :weekly => 5, :monthly => 1
|
41
41
|
person.daily
|
42
|
-
|
42
|
+
=> 3
|
43
43
|
person.counter
|
44
|
-
|
44
|
+
=> 3233
|
45
45
|
|
46
46
|
person = Person.new :counter => 3233
|
47
47
|
person.monthly
|
48
|
-
|
48
|
+
=> 1
|
49
49
|
person.weekly
|
50
|
-
|
50
|
+
=> 5
|
51
51
|
person.monthly = 4
|
52
52
|
person.counter
|
53
|
-
|
53
|
+
=> 3236
|
54
|
+
```
|
55
|
+
|
56
|
+
We can inspsect what this bitstring looks like to converting it like so:
|
54
57
|
|
55
|
-
|
58
|
+
```ruby
|
56
59
|
person.counter.to_s(2)
|
57
60
|
-> 000110010100100
|
61
|
+
|
62
|
+
```
|
63
|
+
|
64
|
+
We also provide convenient methods for resetting and incrementing fields. These methods require active-record and active-relation since they use the "update_attributes" and "update_all" methods.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
peron.reset(:counter, :daily)
|
68
|
+
person.daily
|
69
|
+
=> 0
|
70
|
+
person.monthly
|
71
|
+
=> 4
|
72
|
+
|
73
|
+
person.increment(:counter, :daily)
|
74
|
+
person.daily
|
75
|
+
=> 1
|
76
|
+
|
77
|
+
person.reset(:counter, :daily, :monthly)
|
78
|
+
person.daily
|
79
|
+
=> 0
|
80
|
+
person.monthly
|
81
|
+
=> 0
|
82
|
+
```
|
83
|
+
|
84
|
+
The same thing works with bulk assignment:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
[person1.daily, person2.daily]
|
88
|
+
=> [15, 23]
|
89
|
+
|
90
|
+
Person.reset :counter, :daily
|
91
|
+
[person1.daily, person2.daily]
|
92
|
+
=> [0, 0]
|
93
|
+
|
94
|
+
Person.increment :counter, :daily
|
95
|
+
=> [1, 1]
|
96
|
+
```
|
97
|
+
|
98
|
+
By the way, these methods all work with your chainable active-relation query methods!
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
Person.where(:daily => 0).increment_bitfield(:counter, :daily)
|
58
102
|
```
|
59
103
|
|
60
104
|
One limitation you should be aware of:
|
@@ -65,9 +109,7 @@ Since this technique pins the counters/limits to specific bits, you will need to
|
|
65
109
|
|
66
110
|
I intend to add some more methods in the models for the following features:
|
67
111
|
|
68
|
-
*
|
69
|
-
- This may make the plugin more dependent on an ORM/database
|
70
|
-
* Possibly replace current String-converstion solution with bit-functions
|
112
|
+
* Possibly replace current String-converstion solution with integer bit-functions
|
71
113
|
* Add comparison methods between bitfields on the same column, or between multiple columns
|
72
114
|
* Investigate if there's a use for right/left bit shifting
|
73
115
|
|
data/lib/multi_bit_field.rb
CHANGED
@@ -1,21 +1,28 @@
|
|
1
|
+
# MultiBitField creates convenience methods for using
|
2
|
+
# multiple filum bit-fields with ActiveRecord.
|
3
|
+
# Author:: Aaron Spiegel
|
4
|
+
# Copyright:: Copyright (c) 2012 Aaron Spiegel
|
5
|
+
# License:: MIT License (http://www.opensource.org/licenses/mit-license.php)
|
1
6
|
require 'multi_bit_field/core_ext'
|
2
7
|
|
3
8
|
module MultiBitField
|
4
9
|
module ClassMethods
|
5
|
-
#
|
6
|
-
|
7
|
-
#
|
8
|
-
# Copyright:: Copyright (c) 2012 Aaron Spiegel
|
9
|
-
# License:: MIT License (http://www.opensource.org/licenses/mit-license.php)
|
10
|
+
# alias :reset_bitfields :reset_bitfield
|
11
|
+
|
12
|
+
# Assign bitfields to a column
|
10
13
|
#
|
11
14
|
# +has_bit_field :column, :fields
|
12
15
|
#
|
16
|
+
# @example
|
13
17
|
# class User < ActiveRecord::Base
|
14
18
|
# has_bit_field :counter, :daily => 0..4, :weekly => 5..9, :monthly => 10..14
|
15
19
|
# end
|
16
20
|
#
|
21
|
+
# @param [ Symbol ] column Integer attribute to store bitfields
|
22
|
+
# @param [ Hash ] fields Specify the bitfield name, and the columns
|
23
|
+
# of the bitstring assigned to it
|
17
24
|
def has_bit_field column, fields
|
18
|
-
|
25
|
+
bitfield_setup! column, fields
|
19
26
|
|
20
27
|
fields.each do |field_name, filum|
|
21
28
|
class_eval <<-EVAL
|
@@ -29,24 +36,134 @@ module MultiBitField
|
|
29
36
|
EVAL
|
30
37
|
end
|
31
38
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
39
|
+
|
40
|
+
# Returns the size of the bitfield in number of bits
|
41
|
+
#
|
42
|
+
# +bitfield_size :column
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# user.bitfield_size :counter
|
46
|
+
#
|
47
|
+
# @param [ Symbol ] column_name column name that stores the bitfield integer
|
48
|
+
#
|
49
|
+
def bitfield_size column_name
|
50
|
+
@@bitfields[column_name].values.sum.count
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns a "reset mask" for a list of fields
|
54
|
+
#
|
55
|
+
# +reset_mask_for :fields
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# user.reset_mask_for :field
|
59
|
+
#
|
60
|
+
# @param [ Symbol ] column name of the column these fields are in
|
61
|
+
# @param [ Symbol ] field(s) name of the field(s) for the mask
|
62
|
+
def reset_mask_for column_name, *fields
|
63
|
+
fields.inject("1" * bitfield_size(column_name)) do |mask, field_name|
|
64
|
+
column = @@bitfields[column_name]
|
65
|
+
raise ArgumentError, "Unknown column for bitfield: #{column_name}" if column.nil?
|
66
|
+
raise ArugmentError, "Unknown field: #{field_name} for column #{column_name}" if column[field_name].nil?
|
67
|
+
range = column[field_name]
|
68
|
+
mask[range] = "0" * range.count
|
69
|
+
mask
|
70
|
+
end.to_i(2)
|
35
71
|
end
|
36
72
|
|
73
|
+
# Returns an "increment mask" for a list of fields
|
74
|
+
#
|
75
|
+
# +increment_mask_for :fields
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# user.increment_mask_for :field
|
79
|
+
#
|
80
|
+
# @param [ Symbol ] column name of the column these fields are in
|
81
|
+
# @param [ Symbol ] field(s) name of the field(s) for the mask
|
82
|
+
def increment_mask_for column_name, *fields
|
83
|
+
fields.inject("0" * bitfield_size(column_name)) do |mask, field_name|
|
84
|
+
column = @@bitfields[column_name]
|
85
|
+
raise ArgumentError, "Unknown column for bitfield: #{column_name}" if column.nil?
|
86
|
+
raise ArugmentError, "Unknown field: #{field_name} for column #{column_name}" if column[field_name].nil?
|
87
|
+
range = column[field_name]
|
88
|
+
mask[range.last] = "1"
|
89
|
+
mask
|
90
|
+
end.to_i(2)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Sets one or more bitfields to 0 within a column
|
94
|
+
#
|
95
|
+
# +reset_bitfield :column, :fields
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
# User.reset_bitfield :column, :daily, :monthly
|
99
|
+
#
|
100
|
+
# @param [ Symbol ] column name of the column these fields are in
|
101
|
+
# @param [ Symbol ] field(s) name of the field(s) to reset
|
102
|
+
def reset_bitfields column_name, *fields
|
103
|
+
mask = reset_mask_for column_name, *fields
|
104
|
+
update_all "#{column_name} = #{column_name} & #{mask}"
|
105
|
+
end
|
106
|
+
alias :reset_bitfield :reset_bitfields
|
107
|
+
|
108
|
+
# Increases one or more bitfields by 1 value
|
109
|
+
#
|
110
|
+
# +increment_bitfield :column, :fields
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# user.increment_bitfield :column, :daily, :monthly
|
114
|
+
#
|
115
|
+
# @param [ Symbol ] column name of the column these fields are in
|
116
|
+
# @param [ Symbol ] field(s) name of the field(s) to reset
|
117
|
+
def increment_bitfields column_name, *fields
|
118
|
+
mask = increment_mask_for column_name, *fields
|
119
|
+
update_all "#{column_name} = #{column_name} + #{mask}"
|
120
|
+
end
|
121
|
+
alias :increment_bitfield :increment_bitfields
|
122
|
+
|
37
123
|
private
|
38
124
|
|
39
|
-
def
|
125
|
+
def bitfield_setup! column, fields
|
40
126
|
if defined?(@@bitfields)
|
41
|
-
|
42
|
-
@@bitfields[column] = fields.values.sum.count
|
127
|
+
@@bitfields[column] = fields
|
43
128
|
else
|
44
|
-
@@bitfields = { column => fields
|
129
|
+
@@bitfields = { column => fields }
|
45
130
|
end
|
46
131
|
end
|
47
132
|
end
|
48
133
|
|
49
134
|
module InstanceMethods
|
135
|
+
# Sets one or more bitfields to 0 within a column
|
136
|
+
#
|
137
|
+
# +reset_bitfield :column, :fields
|
138
|
+
#
|
139
|
+
# @example
|
140
|
+
# user.reset_bitfield :column, :daily, :monthly
|
141
|
+
#
|
142
|
+
# @param [ Symbol ] column name of the column these fields are in
|
143
|
+
# @param [ Symbol ] field(s) name of the field(s) to reset
|
144
|
+
def reset_bitfields column_name, *fields
|
145
|
+
mask = self.class.reset_mask_for column_name, *fields
|
146
|
+
self[column_name] = self[column_name] & mask
|
147
|
+
save
|
148
|
+
end
|
149
|
+
alias :reset_bitfield :reset_bitfields
|
150
|
+
|
151
|
+
# Increases one or more bitfields by 1 value
|
152
|
+
#
|
153
|
+
# +increment_bitfield :column, :fields
|
154
|
+
#
|
155
|
+
# @example
|
156
|
+
# user.increment_bitfield :column, :daily, :monthly
|
157
|
+
#
|
158
|
+
# @param [ Symbol ] column name of the column these fields are in
|
159
|
+
# @param [ Symbol ] field(s) name of the field(s) to reset
|
160
|
+
def increment_bitfields column_name, *fields
|
161
|
+
mask = self.class.increment_mask_for column_name, *fields
|
162
|
+
self[column_name] = self[column_name] += mask
|
163
|
+
save
|
164
|
+
end
|
165
|
+
alias :increment_bitfield :increment_bitfields
|
166
|
+
|
50
167
|
private
|
51
168
|
|
52
169
|
# self[column_name].to_s(2) -- converts integer to binary string
|
@@ -54,14 +171,14 @@ module MultiBitField
|
|
54
171
|
# self[column_name].to_s(2)[filum].to_i(2) -- converts it back to an integer
|
55
172
|
def get_bits_for(column, filum)
|
56
173
|
return nil if self[column].nil?
|
57
|
-
length = self.class.
|
174
|
+
length = self.class.bitfield_size column
|
58
175
|
bit_string = self[column].to_i.to_s(2)
|
59
176
|
|
60
177
|
sprintf("%0#{length}d", bit_string)[filum].to_i(2)
|
61
178
|
end
|
62
179
|
|
63
180
|
def set_bits_for(column, filum, value)
|
64
|
-
length = self.class.
|
181
|
+
length = self.class.bitfield_size column
|
65
182
|
bit_string = self[column].to_i.to_s(2)
|
66
183
|
temp_field = sprintf("%0#{length}d", bit_string)
|
67
184
|
|
Binary file
|
data/test/dummy/db/test.sqlite3
CHANGED
Binary file
|
@@ -40,3 +40,19 @@ Migrating to CreatePeople (20120225171132)
|
|
40
40
|
[1m[36m (0.4ms)[0m [1mselect sqlite_version(*)[0m
|
41
41
|
[1m[35m (0.1ms)[0m SELECT "schema_migrations"."version" FROM "schema_migrations"
|
42
42
|
[1m[36m (0.0ms)[0m [1mPRAGMA index_list("people")[0m
|
43
|
+
[1m[36m (0.1ms)[0m [1mSELECT "schema_migrations"."version" FROM "schema_migrations" [0m
|
44
|
+
[1m[35m (0.3ms)[0m select sqlite_version(*)
|
45
|
+
[1m[36m (1.9ms)[0m [1mCREATE TABLE "people" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "birthday" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL) [0m
|
46
|
+
[1m[35m (1.9ms)[0m CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL)
|
47
|
+
[1m[36m (0.0ms)[0m [1mPRAGMA index_list("schema_migrations")[0m
|
48
|
+
[1m[35m (1.7ms)[0m CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
|
49
|
+
[1m[36m (0.1ms)[0m [1mSELECT version FROM "schema_migrations"[0m
|
50
|
+
[1m[35m (1.6ms)[0m INSERT INTO "schema_migrations" (version) VALUES ('20120225171132')
|
51
|
+
[1m[36m (0.1ms)[0m [1mSELECT "schema_migrations"."version" FROM "schema_migrations" [0m
|
52
|
+
[1m[35m (0.3ms)[0m select sqlite_version(*)
|
53
|
+
[1m[36m (4.1ms)[0m [1mCREATE TABLE "people" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "birthday" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL) [0m
|
54
|
+
[1m[35m (2.0ms)[0m CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL)
|
55
|
+
[1m[36m (0.2ms)[0m [1mPRAGMA index_list("schema_migrations")[0m
|
56
|
+
[1m[35m (2.7ms)[0m CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
|
57
|
+
[1m[36m (0.2ms)[0m [1mSELECT version FROM "schema_migrations"[0m
|
58
|
+
[1m[35m (9.2ms)[0m INSERT INTO "schema_migrations" (version) VALUES ('20120225171132')
|