maiha-acts_as_bits 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -41,6 +41,30 @@ Finding methods work only on PostgreSQL, MySQL and SQL Server because we use "su
41
41
  User.find(:all, :conditions=>{:read=>true})
42
42
 
43
43
 
44
+ Advanced
45
+ ========
46
+
47
+ "(column_name)=" enables to fill up values with argument that is true/false.
48
+ This is useful for initial setting like all allow/deny.
49
+
50
+ user = User.new
51
+ user.operations # => "0000"
52
+
53
+ # set all green
54
+ user.operations = true
55
+ user.operations # => "1111"
56
+
57
+ # set all red
58
+ user.operations = false
59
+ user.operations # => "0000"
60
+
61
+
62
+ Changes
63
+ =======
64
+
65
+ 0.2: support dirty columns for Rails2
66
+
67
+
44
68
  Author
45
69
  ======
46
70
 
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the acts_as_bits plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the acts_as_bits plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'ActsAsBits'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "acts_as_bits"
3
+ s.version = "0.2"
4
+ s.date = "2009-01-21"
5
+ s.summary = ""
6
+ s.email = "maiha@wota.jp"
7
+ s.homepage = "http://github.com/maiha/acts_as_bits"
8
+ s.description = ""
9
+ s.has_rdoc = true
10
+ s.authors = ["maiha"]
11
+ s.files = ["README", "Rakefile", "acts_as_bits.gemspec", "init.rb", "install.rb", "lib/acts_as_bits.rb", "tasks/acts_as_bits_tasks.rake", "test/acts_as_bits_test.rb", "test/database.yml", "test/dirty_spec.rb", "test/fixtures/mixin.rb", "test/fixtures/mixins.yml", "test/prefix_test.rb", "test/schema.rb", "test/spec_helper.rb", "test/test_helper.rb"]
12
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'acts_as_bits'
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,191 @@
1
+ require 'active_record'
2
+
3
+ # ActsAsBits
4
+ module ActsAsBits
5
+ def self.rails2?
6
+ ActiveRecord::Base.respond_to?(:sanitize_sql_hash_for_conditions)
7
+ end
8
+
9
+ def self.append_features(base)
10
+ base.extend ClassMethods
11
+ base.extend(rails2? ? Rails2x : Rails1x)
12
+ base.class_eval do
13
+ def self.sanitize_sql_hash(*args)
14
+ sanitize_sql_hash_with_aab(*args)
15
+ end
16
+
17
+ def self.sanitize_sql_hash_for_conditions(*args)
18
+ sanitize_sql_hash_with_aab(*args)
19
+ end
20
+ end
21
+ end
22
+
23
+ module Rails1x
24
+ def sanitize_sql_hash_with_aab(attrs)
25
+ values = []
26
+ conditions = attrs.map do |attr, value|
27
+ if bit_column = bit_columns_hash[attr.to_s]
28
+ flag = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
29
+ "#{bit_column} %s '1'" % (flag ? '=' : '<>')
30
+ else
31
+ values << value
32
+ prefix = "#{table_name}." rescue ''
33
+ "#{prefix}#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
34
+ end
35
+ end.join(' AND ')
36
+ replace_bind_variables(conditions, expand_range_bind_variables(values))
37
+ end
38
+ end
39
+
40
+ module Rails2x
41
+ def sanitize_sql_hash_with_aab(attrs)
42
+ values = []
43
+ conditions = attrs.keys.map do |key|
44
+ value = attrs[key]
45
+ attr = key.to_s
46
+
47
+ # Extract table name from qualified attribute names.
48
+ if attr.include?('.')
49
+ table_name, attr = attr.split('.', 2)
50
+ table_name = connection.quote_table_name(table_name)
51
+ else
52
+ table_name = quoted_table_name
53
+ end
54
+
55
+ if bit_column = bit_columns_hash[attr]
56
+ flag = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
57
+ "#{bit_column} %s '1'" % (flag ? '=' : '<>')
58
+ else
59
+ values << value
60
+ "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
61
+ end
62
+ end.join(' AND ')
63
+ replace_bind_variables(conditions, expand_range_bind_variables(values))
64
+ end
65
+ end
66
+
67
+ module ClassMethods
68
+ def bit_columns_hash
69
+ @bit_columns_hash ||= {}
70
+ end
71
+
72
+ def acts_as_bits(part_id, bit_names, options = {})
73
+ composed_name = part_id.id2name
74
+ singular_name = composed_name.singularize
75
+
76
+ delegate "#{singular_name}_names", :to=>"self.class"
77
+
78
+ if options[:prefix]
79
+ bit_names = bit_names.map do |(name, label)|
80
+ name = "%s_%s" % [singular_name, name]
81
+ [name, label]
82
+ end
83
+ end
84
+
85
+ # true/false fills all values by itself
86
+ module_eval <<-end_eval
87
+ def #{composed_name}=(value)
88
+ case value
89
+ when true then super("1"*#{singular_name}_names.size)
90
+ when false then super("0"*#{singular_name}_names.size)
91
+ else super
92
+ end
93
+ end
94
+ end_eval
95
+
96
+ bit_names.each_with_index do |(name, label), index|
97
+ next if name.blank?
98
+
99
+ # register bit column
100
+ column_name = "COALESCE(SUBSTRING(%s.%s,%d,1),'')" % [table_name, connection.quote_column_name(composed_name), index+1]
101
+ (@bit_columns_hash ||= {})[name.to_s] = column_name
102
+
103
+ module_eval <<-end_eval
104
+ def #{name}
105
+ #{name}?
106
+ end
107
+
108
+ unless label.blank?
109
+ def self.#{name}_label
110
+ #{label.inspect}
111
+ end
112
+ end
113
+
114
+ def #{name}?
115
+ #{composed_name}.to_s[#{index}] == ?1
116
+ end
117
+
118
+ def #{name}=(v)
119
+ v = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(v) ? ?1 : ?0
120
+
121
+ # expand target string automatically
122
+ if #{composed_name}.size < #{singular_name}_names.size
123
+ #{composed_name} << "0" * (#{singular_name}_names.size - #{composed_name}.size)
124
+ end
125
+
126
+ value = #{composed_name}.dup
127
+ value[#{index}] = v
128
+ write_attribute("#{composed_name}", value)
129
+ return v
130
+ end
131
+ end_eval
132
+
133
+ compacted_bit_names = bit_names.select{|(i,)| !i.blank?}
134
+ column_names = compacted_bit_names.map{|(i,)| i.to_s}
135
+ label_names = compacted_bit_names.map{|(n,i)| i.to_s}
136
+
137
+ module_eval <<-end_eval
138
+ def self.#{singular_name}_names
139
+ #{column_names.inspect}
140
+ end
141
+
142
+ if options[:prefix]
143
+
144
+ def #{singular_name}?(name = nil)
145
+ if name
146
+ __send__ "#{singular_name}_" + name.to_s
147
+ else
148
+ #{composed_name}.to_s.include?('1')
149
+ end
150
+ end
151
+
152
+ else
153
+
154
+ def #{singular_name}?(name = nil)
155
+ if name
156
+ __send__ name
157
+ else
158
+ #{composed_name}.to_s.include?('1')
159
+ end
160
+ end
161
+
162
+ end
163
+
164
+ def #{composed_name}
165
+ self.#{composed_name} = "0" * #{column_names.size} if super.blank?
166
+ super
167
+ end
168
+
169
+ def #{composed_name}_hash
170
+ HashWithIndifferentAccess[*#{singular_name}_names.map{|i| [i,__send__(i)]}.flatten]
171
+ end
172
+ end_eval
173
+
174
+ module_eval <<-end_eval
175
+ def self.#{singular_name}_labels
176
+ #{label_names.inspect}
177
+ end
178
+
179
+ def self.#{singular_name}_names_with_labels
180
+ #{bit_names.inspect}
181
+ end
182
+ end_eval
183
+ end
184
+ end
185
+ end # ClassMethods
186
+ end
187
+
188
+ ActiveRecord::Base.class_eval do
189
+ include ActsAsBits
190
+ end
191
+
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :acts_as_bits do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,153 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ActsAsBitsTest < Test::Unit::TestCase
4
+ fixtures :mixins
5
+
6
+ def test_respond
7
+ m = mixins(:bits_00)
8
+
9
+ assert_respond_to m, :admin
10
+ assert_respond_to m, :admin?
11
+ assert_respond_to m, :admin=
12
+
13
+ assert_respond_to m, :composer
14
+ assert_respond_to m, :composer?
15
+ assert_respond_to m, :composer=
16
+ end
17
+
18
+ def test_reader_00
19
+ m = mixins(:bits_00)
20
+
21
+ assert ! m.admin?
22
+ assert ! m.composer?
23
+ end
24
+
25
+ def test_writer_01
26
+ m = mixins(:bits_01)
27
+
28
+ m.admin = true
29
+ m.composer = false
30
+
31
+ # save?
32
+
33
+ assert m.admin?
34
+ assert ! m.composer?
35
+ end
36
+
37
+ def test_reader_to_new_object
38
+ m = Mixin.new
39
+
40
+ assert_equal false, m.admin?
41
+ assert_equal false, m.composer?
42
+ end
43
+
44
+ def test_names
45
+ assert_equal %w( top right bottom left ), Mixin.position_names
46
+ end
47
+
48
+ def test_labels
49
+ assert_equal "TOP", Mixin.top_label
50
+ assert_equal "RIGHT", Mixin.right_label
51
+ assert_equal "BOTTOM", Mixin.bottom_label
52
+ assert_equal "LEFT", Mixin.left_label
53
+
54
+ assert_equal %w( TOP RIGHT BOTTOM LEFT ), Mixin.position_labels
55
+ end
56
+
57
+ def test_names_with_labels
58
+ expected = [
59
+ [:top, "TOP"],
60
+ [:right, "RIGHT"],
61
+ [:bottom, "BOTTOM"],
62
+ [:left, "LEFT"],
63
+ ]
64
+ assert_equal expected, Mixin.position_names_with_labels
65
+ end
66
+
67
+ def test_nil_means_blank_column
68
+ assert_equal %w( flag1 flag3 ), Mixin.blank_flag_names
69
+
70
+ assert_equal false, mixins(:bits_00).flag1
71
+ assert_equal false, mixins(:bits_00).flag3
72
+ assert_equal true , mixins(:bits_10).flag1
73
+ assert_equal false, mixins(:bits_10).flag3
74
+
75
+ assert_equal "0000", Mixin.new.positions
76
+
77
+ assert_equal "00", Mixin.new.blank_flags
78
+ assert_equal false, Mixin.new.flag1
79
+ assert_equal false, Mixin.new.flag3
80
+ end
81
+
82
+ def test_referer_to_out_of_index
83
+ Mixin.delete_all
84
+ mixin = Mixin.create!(:positions=>"11")
85
+ assert_equal true, mixin.top?
86
+ assert_equal true, mixin.right?
87
+ assert_equal false, mixin.bottom?
88
+ assert_equal false, mixin.left?
89
+ end
90
+
91
+ def test_subscribe_to_out_of_index
92
+ Mixin.delete_all
93
+ mixin = Mixin.create!(:positions=>"11")
94
+
95
+ assert_nothing_raised do
96
+ mixin.left = true
97
+ end
98
+
99
+ assert_equal true, mixin.left?
100
+ end
101
+
102
+ def test_functional_accessor
103
+ mixin = Mixin.create!(:positions=>"1010")
104
+ assert_equal true, mixin.position?(:top)
105
+ assert_equal false, mixin.position?(:right)
106
+ assert_equal true, mixin.position?(:bottom)
107
+ assert_equal false, mixin.position?(:left)
108
+ end
109
+
110
+ def test_functional_accessor_without_args
111
+ mixin = Mixin.create!(:positions=>"0001")
112
+ assert_equal true, mixin.position?
113
+
114
+ mixin = Mixin.create!(:positions=>"0000")
115
+ assert_equal false, mixin.position?
116
+
117
+ mixin = Mixin.create!(:positions=>"1111")
118
+ assert_equal true, mixin.position?
119
+ end
120
+
121
+ def test_sanitize_sql_hash
122
+ expected = ["COALESCE(SUBSTRING(mixins.positions,1,1),'') = '1'", "COALESCE(SUBSTRING(mixins.positions,4,1),'') <> '1'"]
123
+ executed = Mixin.__send__(:sanitize_sql_hash, {:top => true, :left => false})
124
+ executed = executed.delete('`"').split(/ AND /).sort
125
+
126
+ assert_equal expected, executed
127
+ end
128
+
129
+ def test_search
130
+ conditions = {:top=>true}
131
+ assert_equal 1, Mixin.count(:conditions=>conditions)
132
+ assert_equal 1, Mixin.find(:all, :conditions=>conditions).size
133
+
134
+ conditions = {:top=>false, :right=>false, :bottom=>false, :left=>false}
135
+ assert_equal 2, Mixin.count(:conditions=>conditions)
136
+ assert_equal 2, Mixin.find(:all, :conditions=>conditions).size
137
+
138
+ conditions = {:top=>true, :right=>true, :bottom=>true, :left=>true}
139
+ assert_equal 1, Mixin.count(:conditions=>conditions)
140
+ assert_equal 1, Mixin.find(:all, :conditions=>conditions).size
141
+ end
142
+
143
+ def test_set_all
144
+ obj = Mixin.new
145
+ assert_equal "00", obj.flags
146
+
147
+ obj.flags = true
148
+ assert_equal "11", obj.flags
149
+
150
+ obj.flags = false
151
+ assert_equal "00", obj.flags
152
+ end
153
+ end
data/test/database.yml ADDED
@@ -0,0 +1,22 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :dbfile: acts_as_bits_plugin_test.sqlite.db
4
+
5
+ sqlite3:
6
+ :adapter: sqlite3
7
+ :dbfile: acts_as_bits_plugin_test.sqlite3.db
8
+
9
+ postgresql:
10
+ :adapter: postgresql
11
+ :username: maiha
12
+ :password:
13
+ :database: test
14
+ :min_messages: ERROR
15
+
16
+ mysql:
17
+ :adapter: mysql
18
+ :host: localhost
19
+ :username: rails
20
+ :password:
21
+ :database: acts_as_bits_plugin_test
22
+
@@ -0,0 +1,25 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe ActsAsBits do
4
+ describe " should mark dirty" do
5
+ before(:each) do
6
+ @obj = Mixin.create!(:flags=>"00")
7
+ end
8
+
9
+ it "when value is changed by setter" do
10
+ @obj.flags # create string object first
11
+ @obj.changed?.should == false
12
+ @obj.admin = true
13
+ @obj.changed?.should == true
14
+ @obj.changes.should == {"flags"=>["00", "10"]}
15
+ end
16
+
17
+ it "when massive assignment" do
18
+ @obj.flags # create string object first
19
+ @obj.changed?.should == false
20
+ @obj.attributes = {:admin => true}
21
+ @obj.changed?.should == true
22
+ @obj.changes.should == {"flags"=>["00", "10"]}
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,10 @@
1
+ class Mixin < ActiveRecord::Base
2
+ acts_as_bits :flags, %w( admin composer )
3
+ acts_as_bits :positions, [
4
+ [:top, "TOP"],
5
+ [:right, "RIGHT"],
6
+ [:bottom, "BOTTOM"],
7
+ [:left, "LEFT"],
8
+ ]
9
+ acts_as_bits :blank_flags, [:flag1, nil, :flag3]
10
+ end
@@ -0,0 +1,20 @@
1
+ bits_00:
2
+ id: 1
3
+ flags: "00"
4
+ positions: "0000"
5
+ blank_flags: "0 0"
6
+ bits_01:
7
+ id: 2
8
+ flags: "01"
9
+ positions: "1111"
10
+ blank_flags: "0 1"
11
+ bits_10:
12
+ id: 3
13
+ flags: "10"
14
+ positions: "0101"
15
+ blank_flags: "1 0"
16
+ bits_11:
17
+ id: 4
18
+ flags: "11"
19
+ positions: ""
20
+ blank_flags: "1 1"
@@ -0,0 +1,52 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class PrefixTest < Test::Unit::TestCase
4
+ class User < ActiveRecord::Base
5
+ set_table_name :mixins
6
+ acts_as_bits :flags, %w( login show ), :prefix=>true
7
+ end
8
+
9
+ def test_not_respond_to_native_name
10
+ user = User.new
11
+
12
+ assert ! user.respond_to?(:login)
13
+ assert ! user.respond_to?(:show)
14
+ end
15
+
16
+ def test_respond_to_prefixed_name
17
+ user = User.new
18
+
19
+ assert_respond_to user, :flag_login?
20
+ assert_respond_to user, :flag_login=
21
+ assert_respond_to user, :flag_login
22
+ assert_respond_to user, :flag_show?
23
+ assert_respond_to user, :flag_show=
24
+ assert_respond_to user, :flag_show
25
+ end
26
+
27
+ def test_respond_to_prefix_method
28
+ user = User.new
29
+ assert_respond_to user, :flag?
30
+ end
31
+
32
+ def test_setter
33
+ user = User.new
34
+ assert_equal false, user.flag_login?
35
+ user.flag_login = true
36
+ assert_equal true , user.flag_login?
37
+ end
38
+
39
+ def test_prefix_method
40
+ user = User.new
41
+ assert_equal false, user.flag?(:login)
42
+ assert_equal false, user.flag?(:show)
43
+ assert_equal false, user.flag?("login")
44
+ assert_equal false, user.flag?("show")
45
+
46
+ user.flag_login = true
47
+ assert_equal true, user.flag?(:login)
48
+ assert_equal false, user.flag?(:show)
49
+ assert_equal true, user.flag?("login")
50
+ assert_equal false, user.flag?("show")
51
+ end
52
+ end
data/test/schema.rb ADDED
@@ -0,0 +1,9 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+
3
+ create_table :mixins, :force => true do |t|
4
+ t.column :flags, :string
5
+ t.column :positions, :string
6
+ t.column :blank_flags, :string
7
+ end
8
+
9
+ end
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'active_record'
4
+
5
+ __DIR__ = File.dirname(__FILE__)
6
+ config = YAML::load_file(__DIR__ + '/database.yml')
7
+ ActiveRecord::Base.configurations = config
8
+ ActiveRecord::Base.establish_connection(ENV['DB'] || 'postgresql')
9
+ load(__DIR__ + "/schema.rb")
10
+
11
+ $:.unshift __DIR__ + "/../lib"
12
+ require 'acts_as_bits'
13
+
14
+ require File.join(__DIR__, "fixtures/mixin")
@@ -0,0 +1,25 @@
1
+ def __DIR__; File.dirname(__FILE__); end
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'active_support'
6
+ require 'active_record'
7
+ require 'active_record/fixtures'
8
+
9
+ config = YAML::load_file(__DIR__ + '/database.yml')
10
+ ActiveRecord::Base.logger = Logger.new(__DIR__ + "/debug.log")
11
+ ActiveRecord::Base.colorize_logging = false
12
+ ActiveRecord::Base.configurations = config
13
+ ActiveRecord::Base.establish_connection(ENV['DB'] || 'postgresql')
14
+
15
+ $:.unshift __DIR__ + '/../lib'
16
+ require __DIR__ + '/../init'
17
+
18
+ # create tables
19
+ load(__DIR__ + "/schema.rb")
20
+
21
+ # insert sample data to the tables from 'fixtures/*.yml'
22
+ Test::Unit::TestCase.fixture_path = __DIR__ + "/fixtures/"
23
+ $LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
24
+ Test::Unit::TestCase.use_instantiated_fixtures = true
25
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maiha-acts_as_bits
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.1"
4
+ version: "0.2"
5
5
  platform: ruby
6
6
  authors:
7
7
  - maiha
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-06-04 00:00:00 -07:00
12
+ date: 2009-01-21 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -23,6 +23,21 @@ extra_rdoc_files: []
23
23
 
24
24
  files:
25
25
  - README
26
+ - Rakefile
27
+ - acts_as_bits.gemspec
28
+ - init.rb
29
+ - install.rb
30
+ - lib/acts_as_bits.rb
31
+ - tasks/acts_as_bits_tasks.rake
32
+ - test/acts_as_bits_test.rb
33
+ - test/database.yml
34
+ - test/dirty_spec.rb
35
+ - test/fixtures/mixin.rb
36
+ - test/fixtures/mixins.yml
37
+ - test/prefix_test.rb
38
+ - test/schema.rb
39
+ - test/spec_helper.rb
40
+ - test/test_helper.rb
26
41
  has_rdoc: true
27
42
  homepage: http://github.com/maiha/acts_as_bits
28
43
  post_install_message:
@@ -45,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
45
60
  requirements: []
46
61
 
47
62
  rubyforge_project:
48
- rubygems_version: 1.0.1
63
+ rubygems_version: 1.2.0
49
64
  signing_key:
50
65
  specification_version: 2
51
66
  summary: ""