mandrews-has-bit-field 1.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ .bundle
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm_gemset_create_on_use_flag=1
2
+ rvm gemset use has-bit-field
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in has-bit-field.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,20 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ has-bit-field (1.0.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ activerecord (2.3.10)
10
+ activesupport (= 2.3.10)
11
+ activesupport (2.3.10)
12
+ sqlite3-ruby (1.3.2)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ activerecord (~> 2.3.5)
19
+ has-bit-field!
20
+ sqlite3-ruby
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Paul Barry
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ has-bit-field
2
+ =============
3
+
4
+ has-bit-field allows you to use one attribute of an object to store a bit field which stores the boolean state for multiple flags.
5
+
6
+ To use this with Active Record, you would first require this gem in `config/environment.rb`:
7
+
8
+ config.gem "has-bit-field"
9
+
10
+ Now in one of your models, you define a bit field like this:
11
+
12
+ class Person < ActiveRecord::Base
13
+ extend HasBitField
14
+ has_bit_field :bit_field, :likes_ice_cream, :plays_golf, :watches_tv, :reads_books
15
+ end
16
+
17
+ This means that your database will have an integer column called `bit_field` which will hold the actual bit field. This will generate getter and setter methods for each of the fields. It will also generate a method that has `_bit` as a suffix which will give you the decimal value of the bit that that field is represented by in the bit field. Also there will be a named scope for that field, as well as a named scope prefixed with `not_`, if class you are adding the bit field to responds to `named_scope`. You can use it like this:
18
+
19
+ $ script/console
20
+ Loading development environment (Rails 2.3.2)
21
+ p =>> p = Person.new
22
+ => #<Person id: nil, bit_field: nil, created_at: nil, updated_at: nil>
23
+ >> p.likes_ice_cream = "true"
24
+ => "true"
25
+ >> p.plays_golf = 1
26
+ => 1
27
+ >> p.save
28
+ => true
29
+ >> p = Person.find(p.id)
30
+ => #<Person id: 1, bit_field: 3, created_at: "2009-07-18 03:04:06", updated_at: "2009-07-18 03:04:06">
31
+ >> p.likes_ice_cream?
32
+ => true
33
+ >> p.reads_books?
34
+ => false
35
+ >> Person.plays_golf_bit
36
+ => 4
37
+ >> p = Person.likes_ice_cream.first
38
+ => #<Person id: 1, bit_field: 3, created_at: "2009-07-18 03:04:06", updated_at: "2009-07-18 03:04:06">
39
+
40
+ One of the great advantages of this approach is that it is easy to add additional flags as your application evolves without the overhead of adding new table columns since a single integer will be able to store at least 31 boolean flags. A simple amendment to the model will create the new flag on the existing integer column 'on-the-fly'. However, the order of the flags is vitally important and you should only ever add new flags on the end.
41
+
42
+ class Person < ActiveRecord::Base
43
+ extend HasBitField
44
+ has_bit_field :bit_field, :likes_ice_cream, :plays_golf, :watches_tv, :reads_books, :nut_allergy
45
+ end
46
+
47
+ The new flag will be evaluated as false for existing rows on the database table until their values are explicitly set. Be careful with the peanuts!
48
+
49
+ Another gotcha to be aware of is when combining a bit field with Active Record's `validates_acceptance_of`. When you call `validates_acceptance_of`, if there is no database column, Active Record will define an `attr_accessor` for that boolean field. If you have already defined the bit field, this will clobber those methods. Also, you need to set the value it's looking for to `true` instead of the default of `"1"`. So here's an example of how to use it:
50
+
51
+ class Person < ActiveRecord::Base
52
+ extend HasBitField
53
+ validates_acceptance_of :read_books, :message => "You must agree to read", :accept => true
54
+ has_bit_field :bit_field, :likes_ice_cream, :plays_golf, :watches_tv, :reads_books
55
+ end
56
+
57
+ Copyright
58
+ ---------
59
+
60
+ Copyright (c) 2009 Paul Barry. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+ Rake::TestTask.new(:test) do |test|
7
+ test.libs << 'lib' << 'test'
8
+ test.pattern = 'test/**/*_test.rb'
9
+ test.verbose = true
10
+ end
11
+
12
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "has-bit-field/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "mandrews-has-bit-field"
7
+ s.version = Has::Bit::Field::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Paul Barry", 'Michael Andrews']
10
+ s.email = ["mail@paulbarry.com"]
11
+ s.homepage = "http://github.com/mandrews/has-bit-field"
12
+ s.summary = "Provides an easy way to work with bit fields in active record"
13
+ s.description = "Provides an easy way to work with bit fields in active record"
14
+
15
+ s.rubyforge_project = "has-bit-field"
16
+ s.add_development_dependency "sqlite3-ruby"
17
+ s.add_development_dependency "activerecord", "~> 2.3.5"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
@@ -0,0 +1,7 @@
1
+ module Has
2
+ module Bit
3
+ module Field
4
+ VERSION = "1.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,54 @@
1
+ module HasBitField
2
+ # The first arguement +bit_field_attribute+ should be a symbol,
3
+ # the name of attribute that will hold the actual bit field
4
+ # all following arguments should also be symbols,
5
+ # which will be the name of each flag in the bit field
6
+ def has_bit_field(bit_field_attribute, *args)
7
+ constant = bit_field_attribute.to_s.pluralize.upcase
8
+ const_set(constant, args).freeze
9
+ args.each_with_index do |field,i|
10
+ class_eval %{
11
+ class << self
12
+ def #{field}_bit
13
+ (1 << #{i})
14
+ end
15
+ end
16
+
17
+ def #{field}
18
+ (#{bit_field_attribute} & self.class.#{field}_bit) != 0
19
+ end
20
+
21
+ alias #{field}? #{field}
22
+
23
+ def #{field}=(v)
24
+ if v.to_s == "true" || v.to_s == "1"
25
+ self.#{bit_field_attribute} = (#{bit_field_attribute} || 0) | self.class.#{field}_bit
26
+ else
27
+ self.#{bit_field_attribute} = (#{bit_field_attribute} || 0) & ~self.class.#{field}_bit
28
+ end
29
+ end
30
+
31
+ def #{field}_was
32
+ (#{bit_field_attribute}_was & self.class.#{field}_bit) != 0
33
+ end
34
+
35
+ def #{field}_changed?
36
+ #{field} != #{field}_was
37
+ end
38
+ }
39
+ if(respond_to?(:named_scope))
40
+ if table_exists? && columns_hash.has_key?(bit_field_attribute.to_s) && columns_hash[bit_field_attribute.to_s].null
41
+ class_eval %{
42
+ named_scope :#{field}, :conditions => ["#{table_name}.#{bit_field_attribute} IS NOT NULL AND (#{table_name}.#{bit_field_attribute} & ?) != 0", #{field}_bit]
43
+ named_scope :not_#{field}, :conditions => ["#{table_name}.#{bit_field_attribute} IS NULL OR (#{table_name}.#{bit_field_attribute} & ?) = 0", #{field}_bit]
44
+ }
45
+ else
46
+ class_eval %{
47
+ named_scope :#{field}, :conditions => ["(#{table_name}.#{bit_field_attribute} & ?) != 0", #{field}_bit]
48
+ named_scope :not_#{field}, :conditions => ["(#{table_name}.#{bit_field_attribute} & ?) = 0", #{field}_bit]
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'has-bit-field'
2
+ ActiveRecord::Base.extend HasBitField
@@ -0,0 +1,215 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ ActiveRecord::Base.establish_connection(
4
+ :adapter => "sqlite3",
5
+ :database => ":memory:"
6
+ )
7
+
8
+ #ActiveRecord::Base.logger = Logger.new(STDOUT)
9
+
10
+ ActiveRecord::Base.connection.create_table(:people) do |t|
11
+ t.integer :bit_field, :null => true
12
+ end
13
+
14
+ class Person < ActiveRecord::Base
15
+ extend HasBitField
16
+
17
+ has_bit_field :bit_field, :likes_ice_cream, :plays_golf, :watches_tv, :reads_books
18
+ end
19
+
20
+
21
+ ActiveRecord::Base.connection.create_table(:skills) do |t|
22
+ t.integer :outdoor_bit_field, :default => 0, :null => false
23
+ t.integer :indoor_bit_field, :default => 0, :null => false
24
+ end
25
+
26
+ class Skill < ActiveRecord::Base
27
+ extend HasBitField
28
+
29
+ validates_acceptance_of :chops_trees, :message => "You have to be a lumberjack", :accept => true
30
+ validates_acceptance_of :plays_piano, :message => "You must be a pianist", :accept => true
31
+
32
+ has_bit_field :outdoor_bit_field, :chops_trees, :builds_fences, :cuts_hedges
33
+ has_bit_field :indoor_bit_field, :plays_piano, :mops_floors, :makes_soup
34
+ end
35
+
36
+ class HasBitFieldTest < Test::Unit::TestCase
37
+
38
+ def test_bit_field
39
+ p = Person.new
40
+ [:likes_ice_cream, :plays_golf, :watches_tv, :reads_books].each do |f|
41
+ assert p.respond_to?("#{f}?"), "Expected #{p.inspect} to respond to #{f}?"
42
+ assert p.respond_to?("#{f}="), "Expected #{p.inspect} to respond to #{f}="
43
+ end
44
+
45
+ assert_equal Person.likes_ice_cream_bit, (1 << 0)
46
+ assert_equal Person.plays_golf_bit, (1 << 1)
47
+ assert_equal Person.watches_tv_bit, (1 << 2)
48
+ assert_equal Person.reads_books_bit, (1 << 3)
49
+
50
+ p.likes_ice_cream = true
51
+ assert p.likes_ice_cream?
52
+ assert !p.plays_golf?
53
+ assert !p.watches_tv?
54
+ assert !p.reads_books?
55
+
56
+ p.likes_ice_cream = true
57
+ assert p.likes_ice_cream?
58
+ assert !p.plays_golf?
59
+ assert !p.watches_tv?
60
+ assert !p.reads_books?
61
+
62
+ p.watches_tv = true
63
+ assert p.likes_ice_cream
64
+ assert !p.plays_golf
65
+ assert p.watches_tv
66
+ assert !p.reads_books
67
+
68
+ p.watches_tv = false
69
+ p.plays_golf = true
70
+ assert p.likes_ice_cream?
71
+ assert p.plays_golf?
72
+ assert !p.watches_tv?
73
+ assert !p.reads_books?
74
+
75
+ p.watches_tv = "1"
76
+ p.reads_books = "true"
77
+ assert p.likes_ice_cream?
78
+ assert p.plays_golf?
79
+ assert p.watches_tv?
80
+ assert p.reads_books?
81
+
82
+ p.likes_ice_cream = nil
83
+ p.plays_golf = 0
84
+ p.watches_tv = "0"
85
+ p.reads_books = "false"
86
+ assert !p.likes_ice_cream?
87
+ assert !p.plays_golf?
88
+ assert !p.watches_tv?
89
+ assert !p.reads_books?
90
+ end
91
+
92
+ def test_named_scopes_on_nullable_column
93
+ Person.delete_all
94
+ a = Person.new
95
+ a.plays_golf = true
96
+ a.reads_books = true
97
+ assert a.save
98
+
99
+ b = Person.new
100
+ b.likes_ice_cream = true
101
+ b.watches_tv = true
102
+ assert b.save
103
+
104
+ c = Person.create! :bit_field => 0
105
+
106
+ assert_equal [b], Person.likes_ice_cream.all(:order => "id")
107
+ assert_equal [a,c], Person.not_likes_ice_cream.all(:order => "id")
108
+
109
+ assert_equal [a], Person.plays_golf.all(:order => "id")
110
+ assert_equal [b,c], Person.not_plays_golf.all(:order => "id")
111
+
112
+ assert_equal [b], Person.watches_tv.all(:order => "id")
113
+ assert_equal [a,c], Person.not_watches_tv.all(:order => "id")
114
+
115
+ assert_equal [a], Person.reads_books.all(:order => "id")
116
+ assert_equal [b,c], Person.not_reads_books.all(:order => "id")
117
+ end
118
+
119
+ def test_named_scopes_on_non_nullable_column
120
+ Skill.delete_all
121
+ a = Skill.new
122
+ a.plays_piano = true
123
+ a.chops_trees = true
124
+ a.mops_floors = true
125
+ assert a.save, a.errors.full_messages.join(", ")
126
+
127
+ b = Skill.new
128
+ b.plays_piano = true
129
+ b.chops_trees = true
130
+ b.makes_soup = true
131
+ assert b.save, b.errors.full_messages.join(", ")
132
+
133
+ c = Skill.create! :plays_piano => true, :chops_trees => true
134
+
135
+ assert_equal [a,b,c], Skill.plays_piano.all(:order => "id")
136
+ assert_equal [], Skill.not_plays_piano.all(:order => "id")
137
+
138
+ assert_equal [a], Skill.mops_floors.all(:order => "id")
139
+ assert_equal [b,c], Skill.not_mops_floors.all(:order => "id")
140
+
141
+ assert_equal [b], Skill.makes_soup.all(:order => "id")
142
+ assert_equal [a,c], Skill.not_makes_soup.all(:order => "id")
143
+ end
144
+
145
+ def test_dirty_attributes
146
+ Person.delete_all
147
+ a = Person.new
148
+ a.plays_golf = true
149
+ a.reads_books = true
150
+ assert a.save
151
+
152
+ a.plays_golf = false
153
+ assert_equal false, a.plays_golf
154
+ assert_equal true, a.plays_golf_was
155
+ assert a.plays_golf_changed?
156
+ assert_equal true, a.reads_books
157
+ assert_equal true, a.reads_books_was
158
+ assert !a.reads_books_changed?
159
+ end
160
+
161
+ def test_ar_validations_no_default
162
+ s = Skill.new(:plays_piano => true, :builds_fences => true, :cuts_hedges => false)
163
+
164
+ assert_equal false, s.chops_trees?
165
+ assert_equal false, s.chops_trees
166
+ assert s.builds_fences?
167
+ assert !s.cuts_hedges?
168
+ assert !s.cuts_hedges
169
+
170
+ assert !s.valid?
171
+ assert s.errors[:chops_trees]
172
+
173
+ s.chops_trees = false
174
+ assert !s.chops_trees?
175
+ assert !s.valid?
176
+ assert s.errors[:chops_trees]
177
+
178
+ s.chops_trees = true
179
+ assert s.chops_trees?
180
+ assert s.valid?
181
+ assert !s.errors[:chops_trees]
182
+ assert s.save
183
+ end
184
+
185
+ def test_ar_validations_with_default
186
+ s = Skill.new(:chops_trees => true, :mops_floors => true, :makes_soup => false)
187
+
188
+ assert_equal false, s.plays_piano?
189
+ assert_equal false, s.plays_piano
190
+ assert s.mops_floors?
191
+ assert !s.makes_soup?
192
+ assert !s.makes_soup
193
+
194
+ assert !s.valid?
195
+ assert s.errors[:plays_piano]
196
+
197
+ s.plays_piano = false
198
+ assert !s.plays_piano?
199
+ assert !s.valid?
200
+ assert s.errors[:plays_piano]
201
+
202
+ s.plays_piano = true
203
+ assert s.plays_piano?
204
+ assert s.valid?
205
+ assert !s.errors[:plays_piano]
206
+ assert s.save
207
+ end
208
+
209
+ def test_defines_constant
210
+ assert_equal Person::BIT_FIELDS, [:likes_ice_cream, :plays_golf, :watches_tv, :reads_books]
211
+ assert_equal Skill::INDOOR_BIT_FIELDS, [:plays_piano, :mops_floors, :makes_soup]
212
+ assert_equal Skill::OUTDOOR_BIT_FIELDS, [:chops_trees, :builds_fences, :cuts_hedges]
213
+ end
214
+
215
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+
8
+ require 'test/unit'
9
+ require 'active_record'
10
+ require File.join(File.dirname(__FILE__), "../rails/init")
11
+ require 'has-bit-field'
12
+
13
+ #ActiveRecord::Base.logger = Logger.new($stdout)
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mandrews-has-bit-field
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 1
10
+ version: 1.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Paul Barry
14
+ - Michael Andrews
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-06-30 00:00:00 -07:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: sqlite3-ruby
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :development
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: activerecord
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 9
45
+ segments:
46
+ - 2
47
+ - 3
48
+ - 5
49
+ version: 2.3.5
50
+ type: :development
51
+ version_requirements: *id002
52
+ description: Provides an easy way to work with bit fields in active record
53
+ email:
54
+ - mail@paulbarry.com
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files: []
60
+
61
+ files:
62
+ - .document
63
+ - .gitignore
64
+ - .rvmrc
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - LICENSE
68
+ - README.md
69
+ - Rakefile
70
+ - has-bit-field.gemspec
71
+ - lib/has-bit-field.rb
72
+ - lib/has-bit-field/version.rb
73
+ - rails/init.rb
74
+ - test/has-bit-field_test.rb
75
+ - test/test_helper.rb
76
+ has_rdoc: true
77
+ homepage: http://github.com/mandrews/has-bit-field
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options: []
82
+
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ requirements: []
104
+
105
+ rubyforge_project: has-bit-field
106
+ rubygems_version: 1.4.2
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Provides an easy way to work with bit fields in active record
110
+ test_files:
111
+ - test/has-bit-field_test.rb
112
+ - test/test_helper.rb