multi_bit_field 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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')
|