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 +10 -0
- data/Gemfile +4 -0
- data/README.rdoc +297 -0
- data/Rakefile +34 -0
- data/flag_shih_tzu.gemspec +23 -0
- data/init.rb +1 -0
- data/lib/flag_shih_tzu.rb +210 -0
- data/lib/flag_shih_tzu/version.rb +3 -0
- data/test/database.yml +17 -0
- data/test/flag_shih_tzu_test.rb +499 -0
- data/test/schema.rb +23 -0
- data/test/test_helper.rb +35 -0
- metadata +92 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|
+
|
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
|
data/test/test_helper.rb
ADDED
@@ -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
|