mandrews-has-bit-field 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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