flag_shih_tzu 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ test/debug.log
2
+ test/flag_shih_tzu_plugin.sqlite3.db
3
+ coverage
4
+ .idea
5
+ .idea/*
6
+ .idea/**/*
7
+ *.gem
8
+ .bundle
9
+ Gemfile.lock
10
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ..gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,297 @@
1
+ =FlagShihTzu
2
+
3
+ A rails plugin to store a collection of boolean attributes in a single
4
+ ActiveRecord column as a bit field.
5
+
6
+ http://github.com/xing/flag_shih_tzu
7
+
8
+ This plugin lets you use a single integer column in an ActiveRecord model
9
+ to store a collection of boolean attributes (flags). Each flag can be used
10
+ almost in the same way you would use any boolean attribute on an
11
+ ActiveRecord object.
12
+
13
+ The benefits:
14
+ * No migrations needed for new boolean attributes. This helps a lot
15
+ if you have very large db-tables where you want to avoid ALTER TABLE whenever
16
+ possible.
17
+ * Only the one integer column needs to be indexed.
18
+
19
+ Using FlagShihTzu, you can add new boolean attributes whenever you want,
20
+ without needing any migration. Just add a new flag to the +has_flags+ call.
21
+
22
+ And just in case you are wondering what "Shih Tzu" means:
23
+ http://en.wikipedia.org/wiki/Shih_Tzu
24
+
25
+
26
+ ==Prerequisites
27
+
28
+ FlagShihTzu assumes that your ActiveRecord model already has an integer field
29
+ to store the flags, which should be defined to not allow NULL values and
30
+ should have a default value of 0 (which means all flags are initially set to
31
+ false).
32
+
33
+ The plugin has been tested with Rails versions from 2.1 to 3.0 and MySQL,
34
+ PostgreSQL and SQLite3 databases. It has been tested with ruby 1.9.2 (with
35
+ Rails 3 only).
36
+
37
+
38
+ ==Installation
39
+
40
+ ===As plugin (Rails 2.x, Rails 3)
41
+
42
+ cd path/to/your/rails-project
43
+ ./script/plugin install git://github.com/xing/flag_shih_tzu.git
44
+
45
+ ===As gem (Rails 3)
46
+
47
+ Add following line to your Gemfile:
48
+
49
+ gem 'flag_shih_tzu', '0.1.0.pre'
50
+
51
+ Make sure to install gem with bundler:
52
+
53
+ bundle install
54
+
55
+ ==Usage
56
+
57
+ ===Defining the flags
58
+
59
+ class Spaceship < ActiveRecord::Base
60
+ include FlagShihTzu
61
+
62
+ has_flags 1 => :warpdrive,
63
+ 2 => :shields,
64
+ 3 => :electrolytes
65
+ end
66
+
67
+ +has_flags+ takes a hash. The keys must be positive integers and represent
68
+ the position of the bit being used to enable or disable the flag.
69
+ <b>The keys must not be changed once in use, or you will get wrong results.</b>
70
+ That is why the plugin forces you to set them explicitly.
71
+ The values are symbols for the flags being created.
72
+
73
+
74
+ ===Using a custom column name
75
+
76
+ The default column name to store the flags is 'flags', but you can provide a
77
+ custom column name using the <tt>:column</tt> option. This allows you to use
78
+ different columns for separate flags:
79
+
80
+ has_flags 1 => :warpdrive,
81
+ 2 => :shields,
82
+ 3 => :electrolytes,
83
+ :column => 'features'
84
+
85
+ has_flags 1 => :spock,
86
+ 2 => :scott,
87
+ 3 => :kirk,
88
+ :column => 'crew'
89
+
90
+
91
+ ===Generated instance methods
92
+
93
+ Calling +has_flags+ as shown above creates the following instance methods
94
+ on Spaceship:
95
+
96
+ Spaceship#warpdrive
97
+ Spaceship#warpdrive?
98
+ Spaceship#warpdrive=
99
+ Spaceship#shields
100
+ Spaceship#shields?
101
+ Spaceship#shields=
102
+ Spaceship#electrolytes
103
+ Spaceship#electrolytes?
104
+ Spaceship#electrolytes=
105
+
106
+
107
+ ===Generated named scopes
108
+
109
+ The following named scopes become available:
110
+
111
+ Spaceship.warpdrive # :conditions => "(spaceships.flags in (1,3,5,7))"
112
+ Spaceship.not_warpdrive # :conditions => "(spaceships.flags not in (1,3,5,7))"
113
+ Spaceship.shields # :conditions => "(spaceships.flags in (2,3,6,7))"
114
+ Spaceship.not_shields # :conditions => "(spaceships.flags not in (2,3,6,7))"
115
+ Spaceship.electrolytes # :conditions => "(spaceships.flags in (4,5,6,7))"
116
+ Spaceship.not_electrolytes # :conditions => "(spaceships.flags not in (4,5,6,7))"
117
+
118
+ If you do not want the named scopes to be defined, set the
119
+ <tt>:named_scopes</tt> option to false when calling +has_flags+:
120
+
121
+ has_flags 1 => :warpdrive, 2 => :shields, 3 => :electrolytes, :named_scopes => false
122
+
123
+ In a Rails 3 application, FlagShihTzu will use <tt>scope</tt> internally to generate
124
+ the scopes. The option on has_flags is still named <tt>:named_scopes</tt> however.
125
+
126
+
127
+ ===Examples for using the generated methods
128
+
129
+ enterprise = Spaceship.new
130
+ enterprise.warpdrive = true
131
+ enterprise.shields = true
132
+ enterprise.electrolytes = false
133
+ enterprise.save
134
+
135
+ if enterprise.shields?
136
+ ...
137
+ end
138
+
139
+ Spaceship.warpdrive.find(:all)
140
+ Spaceship.not_electrolytes.count
141
+ ...
142
+
143
+
144
+ ===How it stores the values
145
+
146
+ As said, FlagShihTzu uses a single integer column to store the values for all
147
+ the defined flags as a bit field.
148
+
149
+ The bit position of a flag corresponds to the given key.
150
+
151
+ This way, we can use bit operators on the stored integer value to set, unset
152
+ and check individual flags.
153
+
154
+ +---+---+---+ +---+---+---+
155
+ | | | | | | | |
156
+ Bit position | 3 | 2 | 1 | | 3 | 2 | 1 |
157
+ (flag key) | | | | | | | |
158
+ +---+---+---+ +---+---+---+
159
+ | | | | | | | |
160
+ Bit value | 4 | 2 | 1 | | 4 | 2 | 1 |
161
+ | | | | | | | |
162
+ +---+---+---+ +---+---+---+
163
+ | e | s | w | | e | s | w |
164
+ | l | h | a | | l | h | a |
165
+ | e | i | r | | e | i | r |
166
+ | c | e | p | | c | e | p |
167
+ | t | l | d | | t | l | d |
168
+ | r | d | r | | r | d | r |
169
+ | o | s | i | | o | s | i |
170
+ | l | | v | | l | | v |
171
+ | y | | e | | y | | e |
172
+ | t | | | | t | | |
173
+ | e | | | | e | | |
174
+ | s | | | | s | | |
175
+ +---+---+---+ +---+---+---+
176
+ | 1 | 1 | 0 | = 4 + 2 = 6 | 1 | 0 | 1 | = 4 + 1 = 5
177
+ +---+---+---+ +---+---+---+
178
+
179
+ Read more about bit fields here: http://en.wikipedia.org/wiki/Bit_field
180
+
181
+
182
+ ===Support for manually building conditions
183
+
184
+ The following class methods may support you when manually building
185
+ ActiveRecord conditions:
186
+
187
+ Spaceship.warpdrive_condition # "(spaceships.flags in (1,3,5,7))"
188
+ Spaceship.not_warpdrive_condition # "(spaceships.flags not in (1,3,5,7))"
189
+ Spaceship.shields_condition # "(spaceships.flags in (2,3,6,7))"
190
+ Spaceship.not_shields_condition # "(spaceships.flags not in (2,3,6,7))"
191
+ Spaceship.electrolytes_condition # "(spaceships.flags in (4,5,6,7))"
192
+ Spaceship.not_electrolytes_condition # "(spaceships.flags not in (4,5,6,7))"
193
+
194
+ These methods also accept a :table_alias option that can be used when
195
+ generating SQL that references the same table more than once:
196
+
197
+ Spaceship.shields_condition(:table_alias => 'evil_spaceships') # "(evil_spaceships.flags in (2,3,6,7))"
198
+
199
+
200
+ ===Choosing a query mode
201
+
202
+ While the default way of building the SQL conditions uses an IN() list
203
+ (as shown above), this approach will not work well for a high number of flags,
204
+ as the value list for IN() grows.
205
+
206
+ For MySQL, depending on your MySQL settings, this can even hit the
207
+ 'max_allowed_packet' limit with the generated query.
208
+
209
+ In this case, consider changing the flag query mode to <tt>:bit_operator</tt>
210
+ instead of <tt>:in_list</tt>, like so:
211
+
212
+ has_flags 1 => :warpdrive,
213
+ 2 => :shields,
214
+ :flag_query_mode => :bit_operator
215
+
216
+ This will modify the generated condition and named_scope methods to use bit
217
+ operators in the SQL instead of an IN() list:
218
+
219
+ Spaceship.warpdrive_condition # "(spaceships.flags & 1 = 1)",
220
+ Spaceship.not_warpdrive_condition # "(spaceships.flags & 1 = 0)",
221
+ Spaceship.shields_condition # "(spaceships.flags & 2 = 2)",
222
+ Spaceship.not_shields_condition # "(spaceships.flags & 2 = 0)",
223
+
224
+ Spaceship.warpdrive # :conditions => "(spaceships.flags & 1 = 1)"
225
+ Spaceship.not_warpdrive # :conditions => "(spaceships.flags & 1 = 0)"
226
+ Spaceship.shields # :conditions => "(spaceships.flags & 2 = 2)"
227
+ Spaceship.not_shields # :conditions => "(spaceships.flags & 2 = 0)"
228
+
229
+ The drawback is that due to the bit operator, this query can not use an index
230
+ on the flags column.
231
+
232
+
233
+ ==Running the plugin tests
234
+
235
+ 1. (Rails 3 only) Add <tt>mysql2</tt>, <tt>pg</tt> and <tt>sqlite3</tt> gems to your Gemfile.
236
+ 1. Install flag_shih_tzu as plugin inside working Rails application.
237
+ 1. Modify <tt>test/database.yml</tt> to fit your test environment.
238
+ 2. If needed, create the test database you configured in <tt>test/database.yml</tt>.
239
+
240
+ Then you can run
241
+
242
+ DB=mysql|postgres|sqlite3 rake test:plugins PLUGIN=flag_shih_tzu
243
+
244
+ from your Rails project root or
245
+
246
+ DB=mysql|postgres|sqlite3 rake
247
+
248
+ from <tt>vendor/plugins/flag_shih_tzu</tt>.
249
+
250
+
251
+ ==Authors
252
+
253
+ {Patryk Peszko}[http://github.com/ppeszko],
254
+ {Sebastian Roebke}[http://github.com/boosty],
255
+ {David Anderson}[http://github.com/alpinegizmo]
256
+ and {Tim Payton}[http://github.com/dizzy42]
257
+
258
+ Please find out more about our work in our
259
+ {tech blog}[http://blog.xing.com/category/english/tech-blog].
260
+
261
+
262
+ ==Contributors
263
+
264
+ {TobiTobes}[http://github.com/rngtng],
265
+ {Martin Stannard}[http://github.com/martinstannard],
266
+ {Ladislav Martincik}[http://github.com/lacomartincik],
267
+ {Peter Boling}[http://github.com/pboling],
268
+ {Daniel Jagszent}[http://github.com/d--j],
269
+ {Thorsten Boettger}[http://github.com/alto],
270
+ {Darren Torpey}[http://github.com/darrentorpey],
271
+ {Joost Baaij}[http://github.com/tilsammans] and
272
+ {Musy Bite}[http://github.com/musybite]
273
+
274
+
275
+ ==License
276
+
277
+ The MIT License
278
+
279
+ Copyright (c) 2009 {XING AG}[http://www.xing.com/]
280
+
281
+ Permission is hereby granted, free of charge, to any person obtaining a copy
282
+ of this software and associated documentation files (the "Software"), to deal
283
+ in the Software without restriction, including without limitation the rights
284
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
285
+ copies of the Software, and to permit persons to whom the Software is
286
+ furnished to do so, subject to the following conditions:
287
+
288
+ The above copyright notice and this permission notice shall be included in
289
+ all copies or substantial portions of the Software.
290
+
291
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
292
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
293
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
294
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
295
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
296
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
297
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ require 'bundler'
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :test
10
+
11
+ desc 'Test the flag_shih_tzu plugin.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+ desc 'Generate documentation for the flag_shih_tzu plugin.'
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = 'FlagShihTzu'
22
+ rdoc.options << '--line-numbers' << '--inline-source'
23
+ rdoc.rdoc_files.include('README.rdoc')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
26
+
27
+ namespace :test do
28
+ desc 'Measures test coverage'
29
+ task :coverage do
30
+ rm_f "coverage"
31
+ system("rcov -Ilib test/*_test.rb")
32
+ system("open coverage/index.html") if PLATFORM['darwin']
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "flag_shih_tzu/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "flag_shih_tzu"
7
+ s.version = FlagShihTzu::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Patryk Peszko", "Sebastian Roebke", "David Anderson", "Tim Payton"]
10
+ s.homepage = "https://github.com/xing/flag_shih_tzu"
11
+ s.summary = %q{A rails plugin to store a collection of boolean attributes in a single ActiveRecord column as a bit field}
12
+ s.description = <<-EODOC
13
+ This plugin lets you use a single integer column in an ActiveRecord model
14
+ to store a collection of boolean attributes (flags). Each flag can be used
15
+ almost in the same way you would use any boolean attribute on an
16
+ ActiveRecord object.
17
+ EODOC
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
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'flag_shih_tzu'
@@ -0,0 +1,210 @@
1
+ module FlagShihTzu
2
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'] # taken from ActiveRecord::ConnectionAdapters::Column
3
+ DEFAULT_COLUMN_NAME = 'flags'
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ class IncorrectFlagColumnException < Exception; end
10
+ class NoSuchFlagQueryModeException < Exception; end
11
+ class NoSuchFlagException < Exception; end
12
+
13
+ module ClassMethods
14
+ def has_flags(*args)
15
+ flag_hash, opts = parse_options(*args)
16
+ opts = {
17
+ :named_scopes => true,
18
+ :column => DEFAULT_COLUMN_NAME,
19
+ :flag_query_mode => :in_list
20
+ }.update(opts)
21
+ colmn = opts[:column]
22
+
23
+ return unless check_flag_column(colmn)
24
+
25
+ # options are stored in a class level hash and apply per-column
26
+ class_inheritable_hash :flag_options
27
+ write_inheritable_attribute(:flag_options, {}) if flag_options.nil?
28
+ flag_options[colmn] = opts
29
+
30
+ # the mappings are stored in this class level hash and apply per-column
31
+ class_inheritable_hash :flag_mapping
32
+ write_inheritable_attribute(:flag_mapping, {}) if flag_mapping.nil?
33
+ flag_mapping[colmn] ||= {}
34
+
35
+ flag_hash.each do |flag_key, flag_name|
36
+ raise ArgumentError, "has_flags: flag keys should be positive integers, and #{flag_key} is not" unless is_valid_flag_key(flag_key)
37
+ raise ArgumentError, "has_flags: flag names should be symbols, and #{flag_name} is not" unless is_valid_flag_name(flag_name)
38
+ next if flag_mapping[colmn][flag_name] & (1 << (flag_key - 1)) # next if already methods defined by flagshitzu
39
+ raise ArgumentError, "has_flags: flag name #{flag_name} already defined, please choose different name" if method_defined?(flag_name)
40
+
41
+ flag_mapping[colmn][flag_name] = 1 << (flag_key - 1)
42
+
43
+ class_eval <<-EVAL
44
+ def #{flag_name}
45
+ flag_enabled?(:#{flag_name}, '#{colmn}')
46
+ end
47
+
48
+ def #{flag_name}?
49
+ flag_enabled?(:#{flag_name}, '#{colmn}')
50
+ end
51
+
52
+ def #{flag_name}=(value)
53
+ FlagShihTzu::TRUE_VALUES.include?(value) ? enable_flag(:#{flag_name}, '#{colmn}') : disable_flag(:#{flag_name}, '#{colmn}')
54
+ end
55
+
56
+ def self.#{flag_name}_condition(options = {})
57
+ sql_condition_for_flag(:#{flag_name}, '#{colmn}', true, options[:table_alias] || self.table_name)
58
+ end
59
+
60
+ def self.not_#{flag_name}_condition
61
+ sql_condition_for_flag(:#{flag_name}, '#{colmn}', false)
62
+ end
63
+ EVAL
64
+
65
+ # Define the named scopes if the user wants them and AR supports it
66
+ if flag_options[colmn][:named_scopes] && respond_to?(named_scope_method)
67
+ class_eval <<-EVAL
68
+ #{named_scope_method} :#{flag_name}, lambda { { :conditions => #{flag_name}_condition } }
69
+ #{named_scope_method} :not_#{flag_name}, lambda { { :conditions => not_#{flag_name}_condition } }
70
+ EVAL
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ def check_flag(flag, colmn)
77
+ raise ArgumentError, "Column name '#{colmn}' for flag '#{flag}' is not a string" unless colmn.is_a?(String)
78
+ raise ArgumentError, "Invalid flag '#{flag}'" if flag_mapping[colmn].nil? || !flag_mapping[colmn].include?(flag)
79
+ end
80
+
81
+ private
82
+
83
+ def parse_options(*args)
84
+ options = args.shift
85
+ if args.size >= 1
86
+ add_options = args.shift
87
+ else
88
+ add_options = options.keys.select {|key| !key.is_a?(Fixnum)}.inject({}) do |hash, key|
89
+ hash[key] = options.delete(key)
90
+ hash
91
+ end
92
+ end
93
+ return options, add_options
94
+ end
95
+
96
+ def check_flag_column(colmn, table_name = self.table_name)
97
+ # If you aren't using ActiveRecord (eg. you are outside rails) then do not fail here
98
+ # If you are using ActiveRecord then you only want to check for the table if the table exists so it won't fail pre-migration
99
+ has_ar = !!defined?(ActiveRecord) && self.respond_to?(:descends_from_active_record?)
100
+ # Supposedly Rails 2.3 takes care of this, but this precaution is needed for backwards compatibility
101
+ has_table = has_ar ? ActiveRecord::Base.connection.tables.include?(table_name) : true
102
+
103
+ logger.warn("Error: Table '#{table_name}' doesn't exist") and return false unless has_table
104
+
105
+ if !has_ar || (has_ar && has_table)
106
+ if found_column = columns.find {|column| column.name == colmn}
107
+ raise IncorrectFlagColumnException, "Warning: Column '#{colmn}'must be of type integer in order to use FlagShihTzu" unless found_column.type == :integer
108
+ else
109
+ # Do not raise an exception since the migration to add the flags column might still be pending
110
+ logger.warn("Warning: Table '#{table_name}' must have an integer column named '#{colmn}' in order to use FlagShihTzu") and return false
111
+ end
112
+ end
113
+
114
+ true
115
+ end
116
+
117
+ def sql_condition_for_flag(flag, colmn, enabled = true, table_name = self.table_name)
118
+ check_flag(flag, colmn)
119
+
120
+ if flag_options[colmn][:flag_query_mode] == :bit_operator
121
+ # use & bit operator directly in the SQL query.
122
+ # This has the drawback of not using an index on the flags colum.
123
+ "(#{table_name}.#{colmn} & #{flag_mapping[colmn][flag]} = #{enabled ? flag_mapping[colmn][flag] : 0})"
124
+ elsif flag_options[colmn][:flag_query_mode] == :in_list
125
+ # use IN() operator in the SQL query.
126
+ # This has the drawback of becoming a big query when you have lots of flags.
127
+ neg = enabled ? "" : "not "
128
+ "(#{table_name}.#{colmn} #{neg}in (#{sql_in_for_flag(flag, colmn).join(',')}))"
129
+ else
130
+ raise NoSuchFlagQueryModeException
131
+ end
132
+ end
133
+
134
+ # returns an array of integers suitable for a SQL IN statement.
135
+ def sql_in_for_flag(flag, colmn)
136
+ val = flag_mapping[colmn][flag]
137
+ num = 2 ** flag_mapping[flag_options[colmn][:column]].length
138
+ (1..num).select {|i| i & val == val}
139
+ end
140
+
141
+ def is_valid_flag_key(flag_key)
142
+ flag_key > 0 && flag_key == flag_key.to_i
143
+ end
144
+
145
+ def is_valid_flag_name(flag_name)
146
+ flag_name.is_a?(Symbol)
147
+ end
148
+
149
+ # Returns the correct method to create a named scope.
150
+ # Use to prevent deprecation notices on Rails 3 when using +named_scope+ instead of +scope+.
151
+ def named_scope_method
152
+ # Can't use respond_to because both AR 2 and 3 respond to both +scope+ and +named_scope+.
153
+ ActiveRecord::VERSION::MAJOR == 2 ? :named_scope : :scope
154
+ end
155
+ end
156
+
157
+ # Performs the bitwise operation so the flag will return +true+.
158
+ def enable_flag(flag, colmn = nil)
159
+ colmn = determine_flag_colmn_for(flag) if colmn.nil?
160
+ self.class.check_flag(flag, colmn)
161
+
162
+ set_flags(self.flags(colmn) | self.class.flag_mapping[colmn][flag], colmn)
163
+ end
164
+
165
+ # Performs the bitwise operation so the flag will return +false+.
166
+ def disable_flag(flag, colmn = nil)
167
+ colmn = determine_flag_colmn_for(flag) if colmn.nil?
168
+ self.class.check_flag(flag, colmn)
169
+
170
+ set_flags(self.flags(colmn) & ~self.class.flag_mapping[colmn][flag], colmn)
171
+ end
172
+
173
+ def flag_enabled?(flag, colmn = nil)
174
+ colmn = determine_flag_colmn_for(flag) if colmn.nil?
175
+ self.class.check_flag(flag, colmn)
176
+
177
+ get_bit_for(flag, colmn) == 0 ? false : true
178
+ end
179
+
180
+ def flag_disabled?(flag, colmn = nil)
181
+ colmn = determine_flag_colmn_for(flag) if colmn.nil?
182
+ self.class.check_flag(flag, colmn)
183
+
184
+ !flag_enabled?(flag, colmn)
185
+ end
186
+
187
+ def flags(colmn = DEFAULT_COLUMN_NAME)
188
+ self[colmn] || 0
189
+ end
190
+
191
+ def set_flags(value, colmn)
192
+ self[colmn] = value
193
+ end
194
+
195
+ private
196
+
197
+ def get_bit_for(flag, colmn)
198
+ self.flags(colmn) & self.class.flag_mapping[colmn][flag]
199
+ end
200
+
201
+ def determine_flag_colmn_for(flag)
202
+ return DEFAULT_COLUMN_NAME if self.class.flag_mapping.nil?
203
+ self.class.flag_mapping.each_pair do |colmn, mapping|
204
+ return colmn if mapping.include?(flag)
205
+ end
206
+ raise NoSuchFlagException.new("determine_flag_colmn_for: Couldn't determine column for your flags!")
207
+ end
208
+
209
+ end
210
+
@@ -0,0 +1,3 @@
1
+ module FlagShihTzu
2
+ VERSION = "0.1.0.pre"
3
+ end
data/test/database.yml ADDED
@@ -0,0 +1,17 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: test/flag_shih_tzu_plugin.sqlite3.db
4
+
5
+ mysql:
6
+ adapter: mysql2
7
+ host: localhost
8
+ username: root
9
+ password:
10
+ database: flag_shih_tzu_plugin_test
11
+
12
+ postgresql:
13
+ adapter: postgresql
14
+ username: postgres
15
+ password:
16
+ database: flag_shih_tzu_plugin_test
17
+ min_messages: ERROR
@@ -0,0 +1,499 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper.rb')
2
+ load_schema
3
+
4
+ class Spaceship < ActiveRecord::Base
5
+ set_table_name 'spaceships'
6
+ include FlagShihTzu
7
+
8
+ has_flags 1 => :warpdrive,
9
+ 2 => :shields,
10
+ 3 => :electrolytes
11
+ end
12
+
13
+ class SpaceshipWithoutNamedScopes < ActiveRecord::Base
14
+ set_table_name 'spaceships'
15
+ include FlagShihTzu
16
+
17
+ has_flags(1 => :warpdrive, :named_scopes => false)
18
+ end
19
+
20
+ class SpaceshipWithoutNamedScopesOldStyle < ActiveRecord::Base
21
+ set_table_name 'spaceships'
22
+ include FlagShihTzu
23
+
24
+ has_flags({1 => :warpdrive}, :named_scopes => false)
25
+ end
26
+
27
+ class SpaceshipWithCustomFlagsColumn < ActiveRecord::Base
28
+ set_table_name 'spaceships_with_custom_flags_column'
29
+ include FlagShihTzu
30
+
31
+ has_flags(1 => :warpdrive, 2 => :hyperspace, :column => 'bits')
32
+ end
33
+
34
+ class SpaceshipWith2CustomFlagsColumn < ActiveRecord::Base
35
+ set_table_name 'spaceships_with_2_custom_flags_column'
36
+ include FlagShihTzu
37
+
38
+ has_flags({ 1 => :warpdrive, 2 => :hyperspace }, :column => 'bits')
39
+ has_flags({ 1 => :jeanlucpicard, 2 => :dajanatroj }, :column => 'commanders')
40
+ end
41
+
42
+ class SpaceshipWithBitOperatorQueryMode < ActiveRecord::Base
43
+ set_table_name 'spaceships'
44
+ include FlagShihTzu
45
+
46
+ has_flags(1 => :warpdrive, 2 => :shields, :flag_query_mode => :bit_operator)
47
+ end
48
+
49
+ class SpaceCarrier < Spaceship
50
+ end
51
+
52
+ # table planets is missing intentionally to see if flagshihtzu handles missing tables gracefully
53
+ class Planet < ActiveRecord::Base
54
+ end
55
+
56
+ class FlagShihTzuClassMethodsTest < Test::Unit::TestCase
57
+
58
+ def setup
59
+ Spaceship.destroy_all
60
+ end
61
+
62
+ def test_has_flags_should_raise_an_exception_when_flag_key_is_negative
63
+ assert_raises ArgumentError do
64
+ eval(<<-EOF
65
+ class SpaceshipWithInvalidFlagKey < ActiveRecord::Base
66
+ set_table_name 'spaceships'
67
+ include FlagShihTzu
68
+
69
+ has_flags({ -1 => :error })
70
+ end
71
+ EOF
72
+ )
73
+ end
74
+ end
75
+
76
+ def test_has_flags_should_raise_an_exception_when_flag_name_already_used
77
+ assert_raises ArgumentError do
78
+ eval(<<-EOF
79
+ class SpaceshipWithAlreadyUsedFlag < ActiveRecord::Base
80
+ set_table_name 'spaceships_with_2_custom_flags_column'
81
+ include FlagShihTzu
82
+
83
+ has_flags({ 1 => :jeanluckpicard }, :column => 'bits')
84
+ has_flags({ 1 => :jeanluckpicard }, :column => 'commanders')
85
+ end
86
+ EOF
87
+ )
88
+ end
89
+ end
90
+
91
+ def test_has_flags_should_raise_an_exception_when_desired_flag_name_method_already_defined
92
+ assert_raises ArgumentError do
93
+ eval(<<-EOF
94
+ class SpaceshipWithAlreadyUsedMethod < ActiveRecord::Base
95
+ set_table_name 'spaceships_with_2_custom_flags_column'
96
+ include FlagShihTzu
97
+
98
+ def jeanluckpicard; end
99
+
100
+ has_flags({ 1 => :jeanluckpicard }, :column => 'bits')
101
+ end
102
+ EOF
103
+ )
104
+ end
105
+ end
106
+
107
+ def test_has_flags_should_not_raise_an_exception_when_flag_name_method_defined_by_flagshitzu
108
+ assert_nothing_raised ArgumentError do
109
+ eval(<<-EOF
110
+ class SpaceshipWithAlreadyUsedMethodByFlagshitzu < ActiveRecord::Base
111
+ set_table_name 'spaceships_with_2_custom_flags_column'
112
+ include FlagShihTzu
113
+
114
+ has_flags({ 1 => :jeanluckpicard }, :column => 'bits')
115
+ has_flags({ 1 => :jeanluckpicard }, :column => 'bits')
116
+ end
117
+ EOF
118
+ )
119
+ end
120
+ end
121
+
122
+ def test_has_flags_should_raise_an_exception_when_flag_name_is_not_a_symbol
123
+ assert_raises ArgumentError do
124
+ eval(<<-EOF
125
+ class SpaceshipWithInvalidFlagName < ActiveRecord::Base
126
+ set_table_name 'spaceships'
127
+ include FlagShihTzu
128
+
129
+ has_flags({ 1 => 'error' })
130
+ end
131
+ EOF
132
+ )
133
+ end
134
+ end
135
+
136
+ def test_should_define_a_sql_condition_method_for_flag_enabled
137
+ assert_equal "(spaceships.flags in (1,3,5,7))", Spaceship.warpdrive_condition
138
+ assert_equal "(spaceships.flags in (2,3,6,7))", Spaceship.shields_condition
139
+ assert_equal "(spaceships.flags in (4,5,6,7))", Spaceship.electrolytes_condition
140
+ end
141
+
142
+ def test_should_accept_a_table_alias_option_for_sql_condition_method
143
+ assert_equal "(old_spaceships.flags in (1,3,5,7))", Spaceship.warpdrive_condition(:table_alias => 'old_spaceships')
144
+ end
145
+
146
+ def test_should_define_a_sql_condition_method_for_flag_enabled_with_2_colmns
147
+ assert_equal "(spaceships_with_2_custom_flags_column.bits in (1,3))", SpaceshipWith2CustomFlagsColumn.warpdrive_condition
148
+ assert_equal "(spaceships_with_2_custom_flags_column.bits in (2,3))", SpaceshipWith2CustomFlagsColumn.hyperspace_condition
149
+ assert_equal "(spaceships_with_2_custom_flags_column.commanders in (1,3))", SpaceshipWith2CustomFlagsColumn.jeanlucpicard_condition
150
+ assert_equal "(spaceships_with_2_custom_flags_column.commanders in (2,3))", SpaceshipWith2CustomFlagsColumn.dajanatroj_condition
151
+ end
152
+
153
+ def test_should_define_a_sql_condition_method_for_flag_not_enabled
154
+ assert_equal "(spaceships.flags not in (1,3,5,7))", Spaceship.not_warpdrive_condition
155
+ assert_equal "(spaceships.flags not in (2,3,6,7))", Spaceship.not_shields_condition
156
+ assert_equal "(spaceships.flags not in (4,5,6,7))", Spaceship.not_electrolytes_condition
157
+ end
158
+
159
+ def test_should_define_a_sql_condition_method_for_flag_enabled_with_custom_table_name
160
+ assert_equal "(custom_spaceships.flags in (1,3,5,7))", Spaceship.send( :sql_condition_for_flag, :warpdrive, 'flags', true, 'custom_spaceships')
161
+ end
162
+
163
+ def test_should_define_a_sql_condition_method_for_flag_enabled_with_2_colmns_not_enabled
164
+ assert_equal "(spaceships_with_2_custom_flags_column.bits not in (1,3))", SpaceshipWith2CustomFlagsColumn.not_warpdrive_condition
165
+ assert_equal "(spaceships_with_2_custom_flags_column.bits not in (2,3))", SpaceshipWith2CustomFlagsColumn.not_hyperspace_condition
166
+ assert_equal "(spaceships_with_2_custom_flags_column.commanders not in (1,3))", SpaceshipWith2CustomFlagsColumn.not_jeanlucpicard_condition
167
+ assert_equal "(spaceships_with_2_custom_flags_column.commanders not in (2,3))", SpaceshipWith2CustomFlagsColumn.not_dajanatroj_condition
168
+ end
169
+
170
+ def test_should_define_a_sql_condition_method_for_flag_enabled_using_bit_operators
171
+ assert_equal "(spaceships.flags & 1 = 1)", SpaceshipWithBitOperatorQueryMode.warpdrive_condition
172
+ assert_equal "(spaceships.flags & 2 = 2)", SpaceshipWithBitOperatorQueryMode.shields_condition
173
+ end
174
+
175
+ def test_should_define_a_sql_condition_method_for_flag_not_enabled_using_bit_operators
176
+ assert_equal "(spaceships.flags & 1 = 0)", SpaceshipWithBitOperatorQueryMode.not_warpdrive_condition
177
+ assert_equal "(spaceships.flags & 2 = 0)", SpaceshipWithBitOperatorQueryMode.not_shields_condition
178
+ end
179
+
180
+ def test_should_define_a_named_scope_for_flag_enabled
181
+ assert_equal(["(spaceships.flags in (1,3,5,7))"], Spaceship.warpdrive.where_values)
182
+ assert_equal(["(spaceships.flags in (2,3,6,7))"], Spaceship.shields.where_values)
183
+ assert_equal(["(spaceships.flags in (4,5,6,7))"], Spaceship.electrolytes.where_values)
184
+ end
185
+
186
+ def test_should_define_a_named_scope_for_flag_not_enabled
187
+ assert_equal(["(spaceships.flags not in (1,3,5,7))"], Spaceship.not_warpdrive.where_values)
188
+ assert_equal(["(spaceships.flags not in (2,3,6,7))"], Spaceship.not_shields.where_values)
189
+ assert_equal(["(spaceships.flags not in (4,5,6,7))"], Spaceship.not_electrolytes.where_values)
190
+ end
191
+
192
+ def test_should_define_a_named_scope_for_flag_enabled_with_2_columns
193
+ assert_equal(["(spaceships_with_2_custom_flags_column.bits in (1,3))"], SpaceshipWith2CustomFlagsColumn.warpdrive.where_values)
194
+ assert_equal(["(spaceships_with_2_custom_flags_column.bits in (2,3))"], SpaceshipWith2CustomFlagsColumn.hyperspace.where_values)
195
+ assert_equal(["(spaceships_with_2_custom_flags_column.commanders in (1,3))"], SpaceshipWith2CustomFlagsColumn.jeanlucpicard.where_values)
196
+ assert_equal(["(spaceships_with_2_custom_flags_column.commanders in (2,3))"], SpaceshipWith2CustomFlagsColumn.dajanatroj.where_values)
197
+ end
198
+
199
+ def test_should_define_a_named_scope_for_flag_not_enabled_with_2_columns
200
+ assert_equal(["(spaceships_with_2_custom_flags_column.bits not in (1,3))"], SpaceshipWith2CustomFlagsColumn.not_warpdrive.where_values)
201
+ assert_equal(["(spaceships_with_2_custom_flags_column.bits not in (2,3))"], SpaceshipWith2CustomFlagsColumn.not_hyperspace.where_values)
202
+ assert_equal(["(spaceships_with_2_custom_flags_column.commanders not in (1,3))"], SpaceshipWith2CustomFlagsColumn.not_jeanlucpicard.where_values)
203
+ assert_equal(["(spaceships_with_2_custom_flags_column.commanders not in (2,3))"], SpaceshipWith2CustomFlagsColumn.not_dajanatroj.where_values)
204
+ end
205
+
206
+ def test_should_define_a_named_scope_for_flag_enabled_using_bit_operators
207
+ assert_equal(["(spaceships.flags & 1 = 1)"], SpaceshipWithBitOperatorQueryMode.warpdrive.where_values)
208
+ assert_equal(["(spaceships.flags & 2 = 2)"], SpaceshipWithBitOperatorQueryMode.shields.where_values)
209
+ end
210
+
211
+ def test_should_define_a_named_scope_for_flag_not_enabled_using_bit_operators
212
+ assert_equal(["(spaceships.flags & 1 = 0)"], SpaceshipWithBitOperatorQueryMode.not_warpdrive.where_values)
213
+ assert_equal(["(spaceships.flags & 2 = 0)"], SpaceshipWithBitOperatorQueryMode.not_shields.where_values)
214
+ end
215
+
216
+ def test_should_return_the_correct_number_of_items_from_a_named_scope
217
+ spaceship = Spaceship.new
218
+ spaceship.enable_flag(:warpdrive)
219
+ spaceship.enable_flag(:shields)
220
+ spaceship.save!
221
+ spaceship.reload
222
+ spaceship_2 = Spaceship.new
223
+ spaceship_2.enable_flag(:warpdrive)
224
+ spaceship_2.save!
225
+ spaceship_2.reload
226
+ spaceship_3 = Spaceship.new
227
+ spaceship_3.enable_flag(:shields)
228
+ spaceship_3.save!
229
+ spaceship_3.reload
230
+ assert_equal 1, Spaceship.not_warpdrive.count
231
+ assert_equal 2, Spaceship.warpdrive.count
232
+ assert_equal 1, Spaceship.not_shields.count
233
+ assert_equal 2, Spaceship.shields.count
234
+ assert_equal 1, Spaceship.warpdrive.shields.count
235
+ assert_equal 0, Spaceship.not_warpdrive.not_shields.count
236
+ end
237
+
238
+ def test_should_not_define_named_scopes_if_not_wanted
239
+ assert !SpaceshipWithoutNamedScopes.respond_to?(:warpdrive)
240
+ assert !SpaceshipWithoutNamedScopesOldStyle.respond_to?(:warpdrive)
241
+ end
242
+
243
+ def test_should_work_with_a_custom_flags_column
244
+ spaceship = SpaceshipWithCustomFlagsColumn.new
245
+ spaceship.enable_flag(:warpdrive)
246
+ spaceship.enable_flag(:hyperspace)
247
+ spaceship.save!
248
+ spaceship.reload
249
+ assert_equal 3, spaceship.flags('bits')
250
+ assert_equal "(spaceships_with_custom_flags_column.bits in (1,3))", SpaceshipWithCustomFlagsColumn.warpdrive_condition
251
+ assert_equal "(spaceships_with_custom_flags_column.bits not in (1,3))", SpaceshipWithCustomFlagsColumn.not_warpdrive_condition
252
+ assert_equal "(spaceships_with_custom_flags_column.bits in (2,3))", SpaceshipWithCustomFlagsColumn.hyperspace_condition
253
+ assert_equal "(spaceships_with_custom_flags_column.bits not in (2,3))", SpaceshipWithCustomFlagsColumn.not_hyperspace_condition
254
+ assert_equal(["(spaceships_with_custom_flags_column.bits in (1,3))"], SpaceshipWithCustomFlagsColumn.warpdrive.where_values)
255
+ assert_equal(["(spaceships_with_custom_flags_column.bits not in (1,3))"], SpaceshipWithCustomFlagsColumn.not_warpdrive.where_values)
256
+ assert_equal(["(spaceships_with_custom_flags_column.bits in (2,3))"], SpaceshipWithCustomFlagsColumn.hyperspace.where_values)
257
+ assert_equal(["(spaceships_with_custom_flags_column.bits not in (2,3))"], SpaceshipWithCustomFlagsColumn.not_hyperspace.where_values)
258
+ end
259
+
260
+ def test_should_not_error_out_when_table_is_not_present
261
+ assert_nothing_raised(ActiveRecord::StatementInvalid) do
262
+ Planet.class_eval do
263
+ include FlagShihTzu
264
+ has_flags(1 => :habitable)
265
+ end
266
+ end
267
+ end
268
+
269
+ end
270
+
271
+ class FlagShihTzuInstanceMethodsTest < Test::Unit::TestCase
272
+
273
+ def setup
274
+ @spaceship = Spaceship.new
275
+ @big_spaceship = SpaceshipWith2CustomFlagsColumn.new
276
+ end
277
+
278
+ def test_should_enable_flag
279
+ @spaceship.enable_flag(:warpdrive)
280
+ assert @spaceship.flag_enabled?(:warpdrive)
281
+ end
282
+
283
+ def test_should_enable_flag_with_2_columns
284
+ @big_spaceship.enable_flag(:warpdrive)
285
+ assert @big_spaceship.flag_enabled?(:warpdrive)
286
+ @big_spaceship.enable_flag(:jeanlucpicard)
287
+ assert @big_spaceship.flag_enabled?(:jeanlucpicard)
288
+ end
289
+
290
+ def test_should_disable_flag
291
+ @spaceship.enable_flag(:warpdrive)
292
+ assert @spaceship.flag_enabled?(:warpdrive)
293
+
294
+ @spaceship.disable_flag(:warpdrive)
295
+ assert @spaceship.flag_disabled?(:warpdrive)
296
+ end
297
+
298
+ def test_should_disable_flag_with_2_columns
299
+ @big_spaceship.enable_flag(:warpdrive)
300
+ assert @big_spaceship.flag_enabled?(:warpdrive)
301
+ @big_spaceship.enable_flag(:jeanlucpicard)
302
+ assert @big_spaceship.flag_enabled?(:jeanlucpicard)
303
+
304
+ @big_spaceship.disable_flag(:warpdrive)
305
+ assert @big_spaceship.flag_disabled?(:warpdrive)
306
+ @big_spaceship.disable_flag(:jeanlucpicard)
307
+ assert @big_spaceship.flag_disabled?(:jeanlucpicard)
308
+ end
309
+
310
+ def test_should_store_the_flags_correctly
311
+ @spaceship.enable_flag(:warpdrive)
312
+ @spaceship.disable_flag(:shields)
313
+ @spaceship.enable_flag(:electrolytes)
314
+
315
+ @spaceship.save!
316
+ @spaceship.reload
317
+
318
+ assert_equal 5, @spaceship.flags
319
+ assert @spaceship.flag_enabled?(:warpdrive)
320
+ assert !@spaceship.flag_enabled?(:shields)
321
+ assert @spaceship.flag_enabled?(:electrolytes)
322
+ end
323
+
324
+ def test_should_store_the_flags_correctly_wiht_2_colmns
325
+ @big_spaceship.enable_flag(:warpdrive)
326
+ @big_spaceship.disable_flag(:hyperspace)
327
+ @big_spaceship.enable_flag(:dajanatroj)
328
+
329
+ @big_spaceship.save!
330
+ @big_spaceship.reload
331
+
332
+ assert_equal 1, @big_spaceship.flags('bits')
333
+ assert_equal 2, @big_spaceship.flags('commanders')
334
+
335
+ assert @big_spaceship.flag_enabled?(:warpdrive)
336
+ assert !@big_spaceship.flag_enabled?(:hyperspace)
337
+ assert @big_spaceship.flag_enabled?(:dajanatroj)
338
+ end
339
+
340
+ def test_enable_flag_should_leave_the_flag_enabled_when_called_twice
341
+ 2.times do
342
+ @spaceship.enable_flag(:warpdrive)
343
+ assert @spaceship.flag_enabled?(:warpdrive)
344
+ end
345
+ end
346
+
347
+ def test_disable_flag_should_leave_the_flag_disabled_when_called_twice
348
+ 2.times do
349
+ @spaceship.disable_flag(:warpdrive)
350
+ assert !@spaceship.flag_enabled?(:warpdrive)
351
+ end
352
+ end
353
+
354
+ def test_should_define_an_attribute_reader_method
355
+ assert_equal false, @spaceship.warpdrive
356
+ end
357
+
358
+ def test_should_define_an_attribute_reader_predicate_method
359
+ assert_equal false, @spaceship.warpdrive?
360
+ end
361
+
362
+ def test_should_define_an_attribute_writer_method
363
+ @spaceship.warpdrive = true
364
+ assert @spaceship.warpdrive
365
+ end
366
+
367
+ def test_should_respect_true_values_like_active_record
368
+ [true, 1, '1', 't', 'T', 'true', 'TRUE'].each do |true_value|
369
+ @spaceship.warpdrive = true_value
370
+ assert @spaceship.warpdrive
371
+ end
372
+
373
+ [false, 0, '0', 'f', 'F', 'false', 'FALSE'].each do |false_value|
374
+ @spaceship.warpdrive = false_value
375
+ assert !@spaceship.warpdrive
376
+ end
377
+ end
378
+
379
+ def test_should_ignore_has_flags_call_if_column_does_not_exist_yet
380
+ assert_nothing_raised do
381
+ eval(<<-EOF
382
+ class SpaceshipWithoutFlagsColumn < ActiveRecord::Base
383
+ set_table_name 'spaceships_without_flags_column'
384
+ include FlagShihTzu
385
+
386
+ has_flags 1 => :warpdrive,
387
+ 2 => :shields,
388
+ 3 => :electrolytes
389
+ end
390
+ EOF
391
+ )
392
+ end
393
+
394
+ assert !SpaceshipWithoutFlagsColumn.method_defined?(:warpdrive)
395
+ end
396
+
397
+ def test_should_ignore_has_flags_call_if_column_not_integer
398
+ assert_raises FlagShihTzu::IncorrectFlagColumnException do
399
+ eval(<<-EOF
400
+ class SpaceshipWithNonIntegerColumn < ActiveRecord::Base
401
+ set_table_name 'spaceships_with_non_integer_column'
402
+ include FlagShihTzu
403
+
404
+ has_flags 1 => :warpdrive,
405
+ 2 => :shields,
406
+ 3 => :electrolytes
407
+ end
408
+ EOF
409
+ )
410
+ end
411
+
412
+ assert !SpaceshipWithoutFlagsColumn.method_defined?(:warpdrive)
413
+ end
414
+
415
+ def test_column_guessing_for_default_column
416
+ assert_equal 'flags', @spaceship.send(:determine_flag_colmn_for, :warpdrive)
417
+ end
418
+
419
+ def test_column_guessing_for_default_column
420
+ assert_raises FlagShihTzu::NoSuchFlagException do
421
+ @spaceship.send(:determine_flag_colmn_for, :xxx)
422
+ end
423
+ end
424
+
425
+ def test_column_guessing_for_2_columns
426
+ assert_equal 'commanders', @big_spaceship.send(:determine_flag_colmn_for, :jeanlucpicard)
427
+ assert_equal 'bits', @big_spaceship.send(:determine_flag_colmn_for, :warpdrive)
428
+ end
429
+
430
+ end
431
+
432
+ class FlagShihTzuDerivedClassTest < Test::Unit::TestCase
433
+
434
+ def setup
435
+ @spaceship = SpaceCarrier.new
436
+ end
437
+
438
+ def test_should_enable_flag
439
+ @spaceship.enable_flag(:warpdrive)
440
+ assert @spaceship.flag_enabled?(:warpdrive)
441
+ end
442
+
443
+ def test_should_disable_flag
444
+ @spaceship.enable_flag(:warpdrive)
445
+ assert @spaceship.flag_enabled?(:warpdrive)
446
+
447
+ @spaceship.disable_flag(:warpdrive)
448
+ assert @spaceship.flag_disabled?(:warpdrive)
449
+ end
450
+
451
+ def test_should_store_the_flags_correctly
452
+ @spaceship.enable_flag(:warpdrive)
453
+ @spaceship.disable_flag(:shields)
454
+ @spaceship.enable_flag(:electrolytes)
455
+
456
+ @spaceship.save!
457
+ @spaceship.reload
458
+
459
+ assert_equal 5, @spaceship.flags
460
+ assert @spaceship.flag_enabled?(:warpdrive)
461
+ assert !@spaceship.flag_enabled?(:shields)
462
+ assert @spaceship.flag_enabled?(:electrolytes)
463
+ end
464
+
465
+ def test_enable_flag_should_leave_the_flag_enabled_when_called_twice
466
+ 2.times do
467
+ @spaceship.enable_flag(:warpdrive)
468
+ assert @spaceship.flag_enabled?(:warpdrive)
469
+ end
470
+ end
471
+
472
+ def test_disable_flag_should_leave_the_flag_disabled_when_called_twice
473
+ 2.times do
474
+ @spaceship.disable_flag(:warpdrive)
475
+ assert !@spaceship.flag_enabled?(:warpdrive)
476
+ end
477
+ end
478
+
479
+ def test_should_define_an_attribute_reader_method
480
+ assert_equal false, @spaceship.warpdrive?
481
+ end
482
+
483
+ def test_should_define_an_attribute_writer_method
484
+ @spaceship.warpdrive = true
485
+ assert @spaceship.warpdrive
486
+ end
487
+
488
+ def test_should_respect_true_values_like_active_record
489
+ [true, 1, '1', 't', 'T', 'true', 'TRUE'].each do |true_value|
490
+ @spaceship.warpdrive = true_value
491
+ assert @spaceship.warpdrive
492
+ end
493
+
494
+ [false, 0, '0', 'f', 'F', 'false', 'FALSE'].each do |false_value|
495
+ @spaceship.warpdrive = false_value
496
+ assert !@spaceship.warpdrive
497
+ end
498
+ end
499
+ end
data/test/schema.rb ADDED
@@ -0,0 +1,23 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :spaceships, :force => true do |t|
3
+ t.string :type, :null => false, :default => 'Spaceship'
4
+ t.integer :flags, :null => false, :default => 0
5
+ t.string :incorrect_flags_column, :null => false, :default => ''
6
+ end
7
+
8
+ create_table :spaceships_with_custom_flags_column, :force => true do |t|
9
+ t.integer :bits, :null => false, :default => 0
10
+ end
11
+
12
+ create_table :spaceships_with_2_custom_flags_column, :force => true do |t|
13
+ t.integer :bits, :null => false, :default => 0
14
+ t.integer :commanders, :null => false, :default => 0
15
+ end
16
+
17
+ create_table :spaceships_without_flags_column, :force => true do |t|
18
+ end
19
+
20
+ create_table :spaceships_with_non_integer_column, :force => true do |t|
21
+ t.string :flags, :null => false, :default => 'A string'
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+ ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'
3
+
4
+ #exend LOAD_PATH for test with- and without Railsframework
5
+ $LOAD_PATH << 'lib/'
6
+ $LOAD_PATH << 'vendor/plugins/flag_shih_tzu/lib/'
7
+
8
+ require 'test/unit'
9
+ require 'yaml'
10
+ require 'logger'
11
+ require 'rubygems'
12
+ gem 'activerecord', '~> 3.0'
13
+ require 'active_record'
14
+ require 'flag_shih_tzu'
15
+
16
+ def load_schema
17
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
18
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
19
+ db_adapter = ENV['DB'] || 'sqlite3'
20
+
21
+ # no DB passed, try sqlite3 by default
22
+ db_adapter ||=
23
+ begin
24
+ require 'sqlite3'
25
+ 'sqlite3'
26
+ rescue MissingSourceFile
27
+ end
28
+
29
+ if db_adapter.nil?
30
+ raise "No DB Adapter selected. Configure test/database.yml and use DB=mysql|postgresql|sqlite3 to pick one. sqlite3 will be used by default (gem install sqlite3-ruby)."
31
+ end
32
+
33
+ ActiveRecord::Base.establish_connection(config[db_adapter])
34
+ load(File.dirname(__FILE__) + "/schema.rb")
35
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flag_shih_tzu
3
+ version: !ruby/object:Gem::Version
4
+ hash: 961915980
5
+ prerelease: 6
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ - pre
11
+ version: 0.1.0.pre
12
+ platform: ruby
13
+ authors:
14
+ - Patryk Peszko
15
+ - Sebastian Roebke
16
+ - David Anderson
17
+ - Tim Payton
18
+ autorequire:
19
+ bindir: bin
20
+ cert_chain: []
21
+
22
+ date: 2011-03-09 00:00:00 +03:00
23
+ default_executable:
24
+ dependencies: []
25
+
26
+ description: |
27
+ This plugin lets you use a single integer column in an ActiveRecord model
28
+ to store a collection of boolean attributes (flags). Each flag can be used
29
+ almost in the same way you would use any boolean attribute on an
30
+ ActiveRecord object.
31
+
32
+ email:
33
+ executables: []
34
+
35
+ extensions: []
36
+
37
+ extra_rdoc_files: []
38
+
39
+ files:
40
+ - .gitignore
41
+ - Gemfile
42
+ - README.rdoc
43
+ - Rakefile
44
+ - flag_shih_tzu.gemspec
45
+ - init.rb
46
+ - lib/flag_shih_tzu.rb
47
+ - lib/flag_shih_tzu/version.rb
48
+ - test/database.yml
49
+ - test/flag_shih_tzu_test.rb
50
+ - test/schema.rb
51
+ - test/test_helper.rb
52
+ has_rdoc: true
53
+ homepage: https://github.com/xing/flag_shih_tzu
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">"
74
+ - !ruby/object:Gem::Version
75
+ hash: 25
76
+ segments:
77
+ - 1
78
+ - 3
79
+ - 1
80
+ version: 1.3.1
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.5.2
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: A rails plugin to store a collection of boolean attributes in a single ActiveRecord column as a bit field
88
+ test_files:
89
+ - test/database.yml
90
+ - test/flag_shih_tzu_test.rb
91
+ - test/schema.rb
92
+ - test/test_helper.rb