flag_shih_tzu 0.1.0.pre

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/.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