bitfields 0.4.0 → 0.4.1

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