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 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
- The methods only require an integer attribute (any ORM will do.) Here's how we'd set this up:
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, :daily => 0..4, :weekly => 5..9, :monthly => 10..14
33
- has_bit_field :limit, :daily => 0..4, :weekly => 5..9, :monthly => 10..14
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
- -> 3
42
+ => 3
43
43
  person.counter
44
- -> 3233
44
+ => 3233
45
45
 
46
46
  person = Person.new :counter => 3233
47
47
  person.monthly
48
- -> 1
48
+ => 1
49
49
  person.weekly
50
- -> 5
50
+ => 5
51
51
  person.monthly = 4
52
52
  person.counter
53
- -> 3236
53
+ => 3236
54
+ ```
55
+
56
+ We can inspsect what this bitstring looks like to converting it like so:
54
57
 
55
- \# to view the binary string, you can convert to base-2
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
- * Add class and instance "bitfield reset" methods
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
 
@@ -1,3 +1,3 @@
1
1
  module MultiBitField
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -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
- # MultiBitField creates convenience methods for using
6
- # multiple filum bit-fields with ActiveRecord.
7
- # Author:: Aaron Spiegel
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
- set_bitfields! column, fields
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
- def bitfields
34
- @@bitfields
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 set_bitfields! column, fields
125
+ def bitfield_setup! column, fields
40
126
  if defined?(@@bitfields)
41
- # set the
42
- @@bitfields[column] = fields.values.sum.count
127
+ @@bitfields[column] = fields
43
128
  else
44
- @@bitfields = { column => fields.values.sum.count }
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.bitfields[column]
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.bitfields[column]
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
Binary file
@@ -40,3 +40,19 @@ Migrating to CreatePeople (20120225171132)
40
40
   (0.4ms) select sqlite_version(*)
41
41
   (0.1ms) SELECT "schema_migrations"."version" FROM "schema_migrations"
42
42
   (0.0ms) PRAGMA index_list("people")
43
+  (0.1ms) SELECT "schema_migrations"."version" FROM "schema_migrations" 
44
+  (0.3ms) select sqlite_version(*)
45
+  (1.9ms) CREATE TABLE "people" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "birthday" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL) 
46
+  (1.9ms) CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL)
47
+  (0.0ms) PRAGMA index_list("schema_migrations")
48
+  (1.7ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
49
+  (0.1ms) SELECT version FROM "schema_migrations"
50
+  (1.6ms) INSERT INTO "schema_migrations" (version) VALUES ('20120225171132')
51
+  (0.1ms) SELECT "schema_migrations"."version" FROM "schema_migrations" 
52
+  (0.3ms) select sqlite_version(*)
53
+  (4.1ms) CREATE TABLE "people" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "birthday" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL) 
54
+  (2.0ms) CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL)
55
+  (0.2ms) PRAGMA index_list("schema_migrations")
56
+  (2.7ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
57
+  (0.2ms) SELECT version FROM "schema_migrations"
58
+  (9.2ms) INSERT INTO "schema_migrations" (version) VALUES ('20120225171132')