bitfields 0.4.0 → 0.4.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/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - ree
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile CHANGED
@@ -1,9 +1,7 @@
1
1
  source :rubygems
2
+ gemspec
2
3
 
3
- group :dev do
4
- gem 'activerecord', ENV['AR']
5
- gem 'sqlite3'
6
- gem 'rake'
7
- gem 'rspec', '~>2'
8
- gem 'jeweler'
9
- end
4
+ gem 'activerecord', ENV['AR']
5
+ gem 'sqlite3'
6
+ gem 'rake'
7
+ gem 'rspec', '~>2'
data/Gemfile.lock CHANGED
@@ -1,3 +1,8 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bitfields (0.4.1)
5
+
1
6
  GEM
2
7
  remote: http://rubygems.org/
3
8
  specs:
@@ -17,12 +22,7 @@ GEM
17
22
  bcrypt-ruby (2.1.4)
18
23
  builder (3.0.0)
19
24
  diff-lcs (1.1.2)
20
- git (1.2.5)
21
25
  i18n (0.6.0)
22
- jeweler (1.6.2)
23
- bundler (~> 1.0)
24
- git (>= 1.2.5)
25
- rake
26
26
  multi_json (1.0.3)
27
27
  rake (0.9.2)
28
28
  rspec (2.6.0)
@@ -41,7 +41,7 @@ PLATFORMS
41
41
 
42
42
  DEPENDENCIES
43
43
  activerecord
44
- jeweler
44
+ bitfields!
45
45
  rake
46
46
  rspec (~> 2)
47
47
  sqlite3
data/Rakefile CHANGED
@@ -1,24 +1,29 @@
1
- task :default do
1
+ require 'bundler/gem_tasks'
2
+
3
+ task :spec do
2
4
  sh "rspec spec"
3
5
  end
4
6
 
5
- task :all do
6
- sh "AR=2.3.12 bundle && bundle exec rake"
7
- sh "AR=3.0.8 bundle && bundle exec rake"
8
- sh "AR=3.1.0.rc4 bundle && bundle exec rake"
7
+ task :default do
8
+ sh "AR=2.3.14 && (bundle check || bundle install) && bundle exec rake spec"
9
+ sh "AR=3.0.12 && (bundle check || bundle install) && bundle exec rake spec"
10
+ sh "AR=3.1.4 && (bundle check || bundle install) && bundle exec rake spec"
11
+ sh "AR=3.2.3 && (bundle check || bundle install) && bundle exec rake spec"
9
12
  end
10
13
 
11
- begin
12
- require 'jeweler'
13
- Jeweler::Tasks.new do |gem|
14
- gem.name = 'bitfields'
15
- gem.summary = "Save migrations and columns by storing multiple booleans in a single integer."
16
- gem.email = "grosser.michael@gmail.com"
17
- gem.homepage = "http://github.com/grosser/#{gem.name}"
18
- gem.authors = ["Michael Grosser"]
19
- end
14
+ # extracted from https://github.com/grosser/project_template
15
+ rule /^version:bump:.*/ do |t|
16
+ sh "git status | grep 'nothing to commit'" # ensure we are not dirty
17
+ index = ['major', 'minor','patch'].index(t.name.split(':').last)
18
+ file = 'lib/bitfields/version.rb'
19
+
20
+ version_file = File.read(file)
21
+ old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
22
+ version_parts[index] = version_parts[index].to_i + 1
23
+ version_parts[2] = 0 if index < 2 # remove patch for minor
24
+ version_parts[1] = 0 if index < 1 # remove minor for major
25
+ new_version = version_parts * '.'
26
+ File.open(file,'w'){|f| f.write(version_file.sub(old_version, new_version)) }
20
27
 
21
- Jeweler::GemcutterTasks.new
22
- rescue LoadError
23
- puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
28
+ sh "bundle && git add #{file} Gemfile.lock && git commit -m 'bump version to #{new_version}'"
24
29
  end
data/Readme.md CHANGED
@@ -26,6 +26,7 @@ Or as Rails plugin: ` rails plugin install git://github.com/grosser/bitfields.gi
26
26
 
27
27
  ### Migration
28
28
  ALWAYS set a default, bitfield queries will not work for NULL
29
+
29
30
  t.integer :my_bits, :default => 0, :null => false
30
31
  OR
31
32
  add_column :users, :my_bits, :integer, :default => 0, :null => false
@@ -33,19 +34,27 @@ ALWAYS set a default, bitfield queries will not work for NULL
33
34
  Examples
34
35
  ========
35
36
  Update all users
37
+
36
38
  User.seller.not_stupid.update_all(User.set_bitfield_sql(:seller => true, :insane => true))
37
39
 
38
40
  Delete the shop when a user is no longer a seller
41
+
39
42
  before_save :delete_shop, :if => lambda{|u| u.changes['seller'] == [true, false]}
40
43
 
41
44
  TIPS
42
45
  ====
43
46
  - [Upgrading] in version 0.2.2 the first field(when not given as hash) used bit 2 -> add a bogus field in first position
47
+ - [Defaults] afaik it is not possible to have some bits true by default (without monkeypatching AR/see [tests](https://github.com/grosser/bitfields/commit/2170dc546e2c4f1187089909a80e8602631d0796)) -> choose a good naming like `xxx_on` / `xxx_off` to use the default 'false'
44
48
  - Never do: "#{bitfield_sql(...)} AND #{bitfield_sql(...)}", merge both into one hash
45
49
  - bit_operator is faster in most cases, use :query_mode => :in_list sparingly
46
50
  - Standard mysql integer is 4 byte -> 32 bitfields
47
51
  - If you are lazy or bad at math you can also do `bitfields :bits, :foo, :bar, :baz`
48
52
 
53
+ Query-mode Benchmark
54
+ =========
55
+ The `:query_mode => :in_list` is slower for most queries and scales mierably with the number of bits.<br/>
56
+ *Stay with the default query-mode*. Only use :in_list if your edge-case shows better performance.
57
+
49
58
  ![performance](http://chart.apis.google.com/chart?chtt=bit-operator+vs+IN+--+with+index&chd=s:CEGIKNPRUW,DEHJLOQSVX,CFHKMPSYXZ,DHJMPSVYbe,DHLPRVZbfi,FKOUZeinsx,FLQWbglqw2,HNTZfkqw19,BDEGHJLMOP,BDEGIKLNOQ,BDFGIKLNPQ,BDFGILMNPR,BDFHJKMOQR,BDFHJLMOQS,BDFHJLNPRT,BDFHJLNPRT&chxt=x,y&chxl=0:|100K|200K|300K|400K|500K|600K|700K|800K|900K|1000K|1:|0|1441.671ms&cht=lc&chs=600x500&chdl=2bits+%28in%29|3bits+%28in%29|4bits+%28in%29|6bits+%28in%29|8bits+%28in%29|10bits+%28in%29|12bits+%28in%29|14bits+%28in%29|2bits+%28bit%29|3bits+%28bit%29|4bits+%28bit%29|6bits+%28bit%29|8bits+%28bit%29|10bits+%28bit%29|12bits+%28bit%29|14bits+%28bit%29&chco=0000ff,0000ee,0000dd,0000cc,0000bb,0000aa,000099,000088,ff0000,ee0000,dd0000,cc0000,bb0000,aa0000,990000,880000)
50
59
 
51
60
  TODO
@@ -59,4 +68,6 @@ Authors
59
68
 
60
69
  [Michael Grosser](http://grosser.it)<br/>
61
70
  michael@grosser.it<br/>
62
- Hereby placed under public domain, do what you want, just do not hold me accountable...
71
+ License: MIT<br/>
72
+ [![Build Status](https://secure.travis-ci.org/grosser/bitfields.png)](http://travis-ci.org/grosser/bitfields)
73
+
data/bitfields.gemspec CHANGED
@@ -1,41 +1,12 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
- # -*- encoding: utf-8 -*-
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ name = "bitfields"
3
+ require "#{name}/version"
5
4
 
6
- Gem::Specification.new do |s|
7
- s.name = %q{bitfields}
8
- s.version = "0.4.0"
9
-
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
5
+ Gem::Specification.new name, Bitfields::VERSION do |s|
6
+ s.summary = "Save migrations and columns by storing multiple booleans in a single integer"
11
7
  s.authors = ["Michael Grosser"]
12
- s.date = %q{2011-06-21}
13
- s.email = %q{grosser.michael@gmail.com}
14
- s.files = [
15
- "Gemfile",
16
- "Gemfile.lock",
17
- "Rakefile",
18
- "Readme.md",
19
- "VERSION",
20
- "benchmark/bit_operator_vs_in.rb",
21
- "bitfields.gemspec",
22
- "lib/bitfields.rb",
23
- "spec/bitfields_spec.rb",
24
- "spec/database.rb",
25
- "spec/spec_helper.rb"
26
- ]
27
- s.homepage = %q{http://github.com/grosser/bitfields}
28
- s.require_paths = ["lib"]
29
- s.rubygems_version = %q{1.6.2}
30
- s.summary = %q{Save migrations and columns by storing multiple booleans in a single integer.}
31
-
32
- if s.respond_to? :specification_version then
33
- s.specification_version = 3
34
-
35
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
36
- else
37
- end
38
- else
39
- end
8
+ s.email = "michael@grosser.it"
9
+ s.homepage = "http://github.com/grosser/#{name}"
10
+ s.files = `git ls-files`.split("\n")
11
+ s.license = 'MIT'
40
12
  end
41
-
data/lib/bitfields.rb CHANGED
@@ -1,15 +1,29 @@
1
+ require 'bitfields/version'
1
2
  require 'active_support'
2
3
  require 'active_support/version'
3
4
 
4
5
  module Bitfields
5
- VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
6
6
  TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'] # taken from ActiveRecord::ConnectionAdapters::Column
7
7
  class DuplicateBitNameError < ArgumentError; end
8
8
 
9
9
  def self.included(base)
10
10
  class << base
11
- attr_accessor :bitfields, :bitfield_options
11
+ attr_accessor :bitfields, :bitfield_options, :bitfield_args
12
+
13
+ # all the args passed into .bitfield so children can initialize from parents
14
+ def bitfield_args
15
+ @bitfield_args ||= []
16
+ end
17
+
18
+ def inherited(klass)
19
+ super
20
+ klass.bitfield_args = bitfield_args.dup
21
+ klass.bitfield_args.each do |column, options|
22
+ klass.send :store_bitfield_values, column, options.dup
23
+ end
24
+ end
12
25
  end
26
+
13
27
  base.extend Bitfields::ClassMethods
14
28
  end
15
29
 
@@ -26,56 +40,70 @@ module Bitfields
26
40
 
27
41
  # AR 3+ -> :scope, below :named_scope
28
42
  def self.ar_scoping_method
29
- return :scope if defined?(ActiveRecord::VERSION::MAJOR) and ActiveRecord::VERSION::MAJOR >= 3
30
- :named_scope
43
+ if defined?(ActiveRecord::VERSION::MAJOR) and ActiveRecord::VERSION::MAJOR >= 3
44
+ :scope
45
+ else
46
+ :named_scope
47
+ end
31
48
  end
32
49
 
33
50
  module ClassMethods
34
51
  def bitfield(column, *args)
35
- # prepare ...
36
52
  column = column.to_sym
37
- options = (args.last.is_a?(Hash) ? args.pop.dup : {}) # since we will modify them...
38
- args.each_with_index{|field,i| options[2**i] = field } # add fields given in normal args to options
39
-
40
- # extract options
41
- self.bitfields ||= {}
42
- self.bitfield_options ||= {}
43
- bitfields[column] = Bitfields.extract_bits(options)
44
- bitfield_options[column] = options
53
+ options = extract_bitfield_options args
54
+ bitfield_args << [column, options.dup]
45
55
 
46
- # add instance methods and scopes
47
- bitfields[column].keys.each do |bit_name|
48
- define_method(bit_name){ bitfield_value(bit_name) }
49
- define_method("#{bit_name}?"){ bitfield_value(bit_name) }
50
- define_method("#{bit_name}="){|value| set_bitfield_value(bit_name, value) }
51
- if options[:scopes] != false
52
- scoping_method = Bitfields.ar_scoping_method
53
- send scoping_method, bit_name, :conditions => bitfield_sql(bit_name => true)
54
- send scoping_method, "not_#{bit_name}", :conditions => bitfield_sql(bit_name => false)
55
- end
56
- end
57
-
58
- include Bitfields::InstanceMethods
56
+ store_bitfield_values column, options
57
+ add_bitfield_methods column, options
59
58
  end
60
59
 
61
60
  def bitfield_column(bit_name)
62
- found = bitfields.detect{|c, bits| bits.keys.include?(bit_name.to_sym) }
61
+ found = bitfields.detect{|_, bits| bits.keys.include?(bit_name.to_sym) }
63
62
  raise "Unknown bitfield #{bit_name}" unless found
64
63
  found.first
65
64
  end
66
65
 
67
66
  def bitfield_sql(bit_values, options={})
68
- bits = group_bits_by_column(bit_values).sort_by{|c,v| c.to_s }
67
+ bits = group_bits_by_column(bit_values).sort_by{|c,_| c.to_s }
69
68
  bits.map{|column, bit_values| bitfield_sql_by_column(column, bit_values, options) } * ' AND '
70
69
  end
71
70
 
72
71
  def set_bitfield_sql(bit_values)
73
- bits = group_bits_by_column(bit_values).sort_by{|c,v| c.to_s }
72
+ bits = group_bits_by_column(bit_values).sort_by{|c,_| c.to_s }
74
73
  bits.map{|column, bit_values| set_bitfield_sql_by_column(column, bit_values) } * ', '
75
74
  end
76
75
 
77
76
  private
78
77
 
78
+ def extract_bitfield_options(args)
79
+ options = (args.last.is_a?(Hash) ? args.pop.dup : {})
80
+ args.each_with_index{|field,i| options[2**i] = field } # add fields given in normal args to options
81
+ options
82
+ end
83
+
84
+ def store_bitfield_values(column, options)
85
+ self.bitfields ||= {}
86
+ self.bitfield_options ||= {}
87
+ bitfields[column] = Bitfields.extract_bits(options)
88
+ bitfield_options[column] = options
89
+ end
90
+
91
+ def add_bitfield_methods(column, options)
92
+ bitfields[column].keys.each do |bit_name|
93
+ define_method(bit_name) { bitfield_value(bit_name) }
94
+ define_method("#{bit_name}?") { bitfield_value(bit_name) }
95
+ define_method("#{bit_name}=") { |value| set_bitfield_value(bit_name, value) }
96
+
97
+ if options[:scopes] != false
98
+ scoping_method = Bitfields.ar_scoping_method
99
+ send scoping_method, bit_name, :conditions => bitfield_sql(bit_name => true)
100
+ send scoping_method, "not_#{bit_name}", :conditions => bitfield_sql(bit_name => false)
101
+ end
102
+ end
103
+
104
+ include Bitfields::InstanceMethods
105
+ end
106
+
79
107
  def bitfield_sql_by_column(column, bit_values, options={})
80
108
  mode = options[:query_mode] || (bitfield_options[column][:query_mode] || :bit_operator)
81
109
  case mode
@@ -122,13 +150,13 @@ module Bitfields
122
150
 
123
151
  module InstanceMethods
124
152
  def bitfield_values(column)
125
- Hash[self.class.bitfields[column.to_sym].map{|bit_name, bit| [bit_name, bitfield_value(bit_name)]}]
153
+ Hash[self.class.bitfields[column.to_sym].map{|bit_name, _| [bit_name, bitfield_value(bit_name)]}]
126
154
  end
127
155
 
128
156
  private
129
157
 
130
158
  def bitfield_value(bit_name)
131
- column, bit, current_value = bitfield_info(bit_name)
159
+ _, bit, current_value = bitfield_info(bit_name)
132
160
  current_value & bit != 0
133
161
  end
134
162
 
@@ -0,0 +1,3 @@
1
+ module Bitfields
2
+ Version = VERSION = "0.4.1"
3
+ end
@@ -1,4 +1,4 @@
1
- require 'spec/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  class User < ActiveRecord::Base
4
4
  include Bitfields
@@ -23,9 +23,26 @@ class UserWithoutScopes < ActiveRecord::Base
23
23
  bitfield :bits, 1 => :seller, 2 => :insane, 4 => :stupid, :scopes => false
24
24
  end
25
25
 
26
+ class UserWithoutSetBitfield < ActiveRecord::Base
27
+ set_table_name 'users'
28
+ include Bitfields
29
+ end
30
+
26
31
  class InheritedUser < User
27
32
  end
28
33
 
34
+ class GrandchildInheritedUser < InheritedUser
35
+ end
36
+
37
+ # other children should not disturb the inheritance
38
+ class OtherInheritedUser < UserWithoutSetBitfield
39
+ set_table_name 'users'
40
+ bitfield :bits, 1 => :seller_inherited
41
+ end
42
+
43
+ class InheritedUserWithoutSetBitfield < UserWithoutSetBitfield
44
+ end
45
+
29
46
  class OverwrittenUser < User
30
47
  bitfield :bits, 1 => :seller_inherited
31
48
  end
@@ -57,6 +74,16 @@ class ManyBitsUser < User
57
74
  set_table_name 'users'
58
75
  end
59
76
 
77
+ class InitializedUser < User
78
+ set_table_name 'users'
79
+ bitfield :bits, 1 => :seller, 2 => :insane, 4 => :stupid, :scopes => false
80
+
81
+ after_initialize do
82
+ self.seller = true
83
+ self.insane = false
84
+ end
85
+ end
86
+
60
87
  describe Bitfields do
61
88
  before do
62
89
  User.delete_all
@@ -346,12 +373,48 @@ describe Bitfields do
346
373
  OverwrittenUser.bitfields.should == {:bits=>{:seller_inherited=>1}}
347
374
  end
348
375
 
349
- xit "knows inherited values when overwriting" do
350
- OverwrittenUser.bitfield_column(:seller).should == :bits
376
+ it "knows overwritten values when overwriting" do
377
+ OverwrittenUser.bitfield_column(:seller_inherited).should == :bits
351
378
  end
352
379
 
353
- xit "knows inherited values without overwriting" do
380
+ it "does not know old values when overwriting" do
381
+ expect{
382
+ OverwrittenUser.bitfield_column(:seller)
383
+ }.to raise_error
384
+ end
385
+
386
+ it "knows inherited values without overwriting" do
354
387
  InheritedUser.bitfield_column(:seller).should == :bits
355
388
  end
389
+
390
+ it "has inherited scopes" do
391
+ InheritedUser.should respond_to(:not_seller)
392
+ end
393
+
394
+ it "has inherited methods" do
395
+ InheritedUser.new.should respond_to(:seller?)
396
+ end
397
+
398
+ it "knows grandchild inherited values without overwriting" do
399
+ GrandchildInheritedUser.bitfield_column(:seller).should == :bits
400
+ end
401
+
402
+ it "inherits no bitfields for a user without bitfields set" do
403
+ InheritedUserWithoutSetBitfield.bitfields.should be_nil
404
+ end
405
+ end
406
+
407
+ describe 'initializers' do
408
+ it "sets defaults" do
409
+ InitializedUser.new.seller.should == true
410
+ InitializedUser.new.insane.should == false
411
+ end
412
+
413
+ it "can overwrite defaults in new" do
414
+ pending do
415
+ InitializedUser.new(:seller => false).seller.should == false
416
+ InitializedUser.new(:insane => true).insane.should == true
417
+ end
418
+ end
356
419
  end
357
420
  end
data/spec/spec_helper.rb CHANGED
@@ -10,4 +10,4 @@ require 'timeout'
10
10
  require 'active_record'
11
11
  puts "Using ActiveRecord #{ActiveRecord::VERSION::STRING}"
12
12
 
13
- require 'spec/database'
13
+ require File.expand_path('../database', __FILE__)
metadata CHANGED
@@ -1,77 +1,63 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: bitfields
3
- version: !ruby/object:Gem::Version
4
- hash: 15
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 4
9
- - 0
10
- version: 0.4.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Michael Grosser
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-06-21 00:00:00 +02:00
19
- default_executable:
12
+ date: 2012-05-26 00:00:00.000000000 Z
20
13
  dependencies: []
21
-
22
14
  description:
23
- email: grosser.michael@gmail.com
15
+ email: michael@grosser.it
24
16
  executables: []
25
-
26
17
  extensions: []
27
-
28
18
  extra_rdoc_files: []
29
-
30
- files:
19
+ files:
20
+ - .travis.yml
31
21
  - Gemfile
32
22
  - Gemfile.lock
33
23
  - Rakefile
34
24
  - Readme.md
35
- - VERSION
36
25
  - benchmark/bit_operator_vs_in.rb
37
26
  - bitfields.gemspec
38
27
  - lib/bitfields.rb
28
+ - lib/bitfields/version.rb
39
29
  - spec/bitfields_spec.rb
40
30
  - spec/database.rb
41
31
  - spec/spec_helper.rb
42
- has_rdoc: true
43
32
  homepage: http://github.com/grosser/bitfields
44
- licenses: []
45
-
33
+ licenses:
34
+ - MIT
46
35
  post_install_message:
47
36
  rdoc_options: []
48
-
49
- require_paths:
37
+ require_paths:
50
38
  - lib
51
- required_ruby_version: !ruby/object:Gem::Requirement
39
+ required_ruby_version: !ruby/object:Gem::Requirement
52
40
  none: false
53
- requirements:
54
- - - ">="
55
- - !ruby/object:Gem::Version
56
- hash: 3
57
- segments:
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ segments:
58
46
  - 0
59
- version: "0"
60
- required_rubygems_version: !ruby/object:Gem::Requirement
47
+ hash: 4196443429503694670
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
49
  none: false
62
- requirements:
63
- - - ">="
64
- - !ruby/object:Gem::Version
65
- hash: 3
66
- segments:
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ segments:
67
55
  - 0
68
- version: "0"
56
+ hash: 4196443429503694670
69
57
  requirements: []
70
-
71
58
  rubyforge_project:
72
- rubygems_version: 1.6.2
59
+ rubygems_version: 1.8.24
73
60
  signing_key:
74
61
  specification_version: 3
75
- summary: Save migrations and columns by storing multiple booleans in a single integer.
62
+ summary: Save migrations and columns by storing multiple booleans in a single integer
76
63
  test_files: []
77
-
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.4.0