Permalink
Browse files

merged Pull Request #18 from ddidier:master

  • Loading branch information...
2 parents 6be0aab + cedb7c4 commit 2980ef33ecf539b70c7b72f55e6c3b17f1e518a4 @pboling committed Nov 6, 2012
Showing with 142 additions and 20 deletions.
  1. BIN .DS_Store
  2. +1 −0 .gitignore
  3. +17 −0 CHANGELOG
  4. +35 −4 README.rdoc
  5. +1 −0 flag_shih_tzu.gemspec
  6. +41 −15 lib/flag_shih_tzu.rb
  7. +1 −1 lib/flag_shih_tzu/version.rb
  8. +40 −0 test/flag_shih_tzu_test.rb
  9. +6 −0 test/schema.rb
View
Binary file not shown.
View
@@ -1,4 +1,5 @@
test/debug.log
+test/flag_shih_tzu_plugin.sqlite3.db
coverage
.idea
.idea/*
View
@@ -0,0 +1,17 @@
+Version 0.3.0 - NOV.05.2012 - first version maintained by Peter Boling
+
+* ClassWithHasFlags.set_#{flag_name}_sql # Returns the sql string for setting a flag for use in customized SQL
+* ClassWithHasFlags.unset_#{flag_name}_sql # Returns the sql string for unsetting a flag for use in customized SQL
+* ClassWithHasFlags.flag_columns # Returns the column_names used by FlagShihTzu as bit fields
+* has_flags :strict => true # DuplicateFlagColumnException raised when a single DB column is declared as a flag column twice
+* Less verbosity for expected conditions when the DB connection for the class is unavailable.
+* Tests for additional features, but does not change any behavior of 0.2.3 / 0.2.4 by default.
+* Easily migrate from 0.2.3 / 0.2.4. Goal is no code changes required. Minor version bump to encourage caution.
+
+Version 0.2.4 - NOV.05.2012 - released last few changes from XING master
+
+* Fix deprecation warning for set_table_name
+* Optional bang methods
+* Complete Ruby 1.9(\.[^1]) and Rails 3.2.X compatibility
+
+Version 0.2.3 - last version maintained by XING AG
View
@@ -1,3 +1,22 @@
+== Change of Ownership and 0.3.0 Release Notes
+
+FlagShihTzu was originally a {XING AG}[http://www.xing.com/] project. {Peter Boling}[http://peterboling.com] was a long time contributor and watcher of the project.
+In September 2012 XING transferred ownership of the project to Peter Boling. Peter Boling had been maintaining a
+fork with extended capabilities. These additional features become a part of the 0.3 line. The 0.2 line of the gem will
+remain true to XING's original. The 0.3 line aims to maintain complete parity and compatibility with XING's original as
+well. I will continue to monitor other forks for original ideas and improvements. Pull requests are welcome, but please
+rebase your work onto the current master to make integration easier.
+
+Some new things in the 0.3 line:
+
+* ClassWithHasFlags.set_#{flag_name}_sql # Returns the sql string for setting a flag for use in customized SQL
+* ClassWithHasFlags.unset_#{flag_name}_sql # Returns the sql string for unsetting a flag for use in customized SQL
+* ClassWithHasFlags.flag_columns # Returns the column_names used by FlagShihTzu as bit fields
+* has_flags :strict => true # DuplicateFlagColumnException raised when a single DB column is declared as a flag column twice
+* Less verbosity for expected conditions when the DB connection for the class is unavailable.
+* Tests for additional features, but does not change any behavior of 0.2 versions by default.
+* Easily migrate from 0.2 versions. No code changes required.
+
=FlagShihTzu
Bit fields for ActiveRecord
@@ -28,7 +47,7 @@ http://en.wikipedia.org/wiki/Shih_Tzu
==Build status
-{<img src="https://secure.travis-ci.org/xing/flag_shih_tzu.png" />}[http://travis-ci.org/xing/flag_shih_tzu]
+{<img src="https://secure.travis-ci.org/pboling/flag_shih_tzu.png" />}[http://travis-ci.org/pboling/flag_shih_tzu]
==Prerequisites
@@ -181,7 +200,14 @@ on Spaceship:
Spaceship#selected_electrolytes=
Spaceship#has_electrolyte?
-Opionally, you can set the <tt>:bang_methods</tt> option to true to enable the bang methods:
+===Generated class methods
+
+Calling +has_flags+ as shown above creates the following class methods
+on Spaceship:
+
+ Spaceship.flag_columns # [:features, :crew]
+
+Optionally, you can set the <tt>:bang_methods</tt> option to true to enable the bang methods:
Spaceship#electrolytes!
Spaceship#not_electrolytes!
@@ -308,22 +334,27 @@ specify which config from <tt>test/database.yml</tt> to use, e.g.:
==Authors
+{Peter Boling}[http://github.com/pboling],
{Patryk Peszko}[http://github.com/ppeszko],
{Sebastian Roebke}[http://github.com/boosty],
{David Anderson}[http://github.com/alpinegizmo],
{Tim Payton}[http://github.com/dizzy42]
and a helpful group of
-{contributors}[https://github.com/xing/flag_shih_tzu/contributors].
+{contributors}[https://github.com/pboling/flag_shih_tzu/contributors].
Thanks!
-Please find out more about our work in our
+Find out more about Peter Boling's work
+{PeterBoling.com}[http://peterboling.com/].
+
+Find out more about XING
{Devblog}[http://devblog.xing.com/].
==License
The MIT License
+Copyright (c) 2012 {Peter Boling}[http://www.peterboling.com/]
Copyright (c) 2011 {XING AG}[http://www.xing.com/]
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -27,5 +27,6 @@ ActiveRecord object.
s.add_development_dependency "bundler"
s.add_development_dependency "rdoc", ">= 2.4.2"
s.add_development_dependency "rake"
+ #s.add_development_dependency "rcov"
s.add_development_dependency "sqlite3"
end
View
@@ -12,19 +12,22 @@ def self.included(base)
base.extend(ClassMethods)
base.class_attribute :flag_options unless defined?(base.flag_options)
base.class_attribute :flag_mapping unless defined?(base.flag_mapping)
+ base.class_attribute :flag_columns unless defined?(base.flag_columns)
end
class IncorrectFlagColumnException < Exception; end
class NoSuchFlagQueryModeException < Exception; end
class NoSuchFlagException < Exception; end
+ class DuplicateFlagColumnException < Exception; end
module ClassMethods
def has_flags(*args)
flag_hash, opts = parse_options(*args)
opts = {
:named_scopes => true,
:column => DEFAULT_COLUMN_NAME,
- :flag_query_mode => :in_list
+ :flag_query_mode => :in_list,
+ :strict => false
}.update(opts)
colmn = opts[:column].to_s
@@ -36,8 +39,14 @@ def has_flags(*args)
# the mappings are stored in this class level hash and apply per-column
self.flag_mapping ||= {}
+ #If we already have an instance of the same column in the flag_mapping, then there is a double definition on a column
+ raise DuplicateFlagColumnException if opts[:strict] && !self.flag_mapping[colmn].nil?
self.flag_mapping[colmn] ||= {}
+ # keep track of which flag columns are defined on this class
+ self.flag_columns ||= []
+ self.flag_columns << colmn
+
flag_hash.each do |flag_key, flag_name|
raise ArgumentError, "has_flags: flag keys should be positive integers, and #{flag_key} is not" unless is_valid_flag_key(flag_key)
raise ArgumentError, "has_flags: flag names should be symbols, and #{flag_name} is not" unless is_valid_flag_name(flag_name)
@@ -75,6 +84,14 @@ def self.#{flag_name}_condition(options = {})
def self.not_#{flag_name}_condition
sql_condition_for_flag(:#{flag_name}, '#{colmn}', false)
end
+
+ def self.set_#{flag_name}_sql
+ sql_set_for_flag(:#{flag_name}, '#{colmn}', true)
+ end
+
+ def self.unset_#{flag_name}_sql
+ sql_set_for_flag(:#{flag_name}, '#{colmn}', false)
+ end
EVAL
if colmn != DEFAULT_COLUMN_NAME
@@ -155,39 +172,42 @@ def parse_options(*args)
return options, add_options
end
- def check_flag_column(colmn, table_name = self.table_name)
+ def check_flag_column(colmn, custom_table_name = self.table_name)
# If you aren't using ActiveRecord (eg. you are outside rails) then do not fail here
# 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
has_ar = !!defined?(ActiveRecord) && self.respond_to?(:descends_from_active_record?)
# Supposedly Rails 2.3 takes care of this, but this precaution is needed for backwards compatibility
- has_table = has_ar ? connection.tables.include?(table_name) : true
-
- logger.warn("Error: Table '#{table_name}' doesn't exist") and return false unless has_table
-
- if !has_ar || (has_ar && has_table)
- if found_column = columns.find {|column| column.name == colmn}
- raise IncorrectFlagColumnException, "Warning: Column '#{colmn}'must be of type integer in order to use FlagShihTzu" unless found_column.type == :integer
- else
- # Do not raise an exception since the migration to add the flags column might still be pending
- logger.warn("Warning: Table '#{table_name}' must have an integer column named '#{colmn}' in order to use FlagShihTzu") and return false
+ has_table = has_ar ? ActiveRecord::Base.connection.tables.include?(custom_table_name) : true
+
+ if has_table
+ found_column = columns.find {|column| column.name == colmn}
+ #If you have not yet run the migration that adds the 'flags' column then we don't want to fail, because we need to be able to run the migration
+ #If the column is there but is of the wrong type, then we must fail, because flag_shih_tzu will not work
+ if found_column.nil?
+ logger.warn("Error: Column '#{colmn}' doesn't exist on table '#{custom_table_name}'. Did you forget to run migrations?") and return false
+ elsif found_column.type != :integer
+ raise IncorrectFlagColumnException.new("Table '#{custom_table_name}' must have an integer column named '#{colmn}' in order to use FlagShihTzu.") and return false
end
+ else
+ # ActiveRecord gem probably hasn't loaded yet?
+ logger.warn("FlagShihTzu#has_flags: Table '#{custom_table_name}' doesn't exist. Have all migrations been run?") and return false
end
true
end
- def sql_condition_for_flag(flag, colmn, enabled = true, table_name = self.table_name)
+ def sql_condition_for_flag(flag, colmn, enabled = true, custom_table_name = self.table_name)
check_flag(flag, colmn)
if flag_options[colmn][:flag_query_mode] == :bit_operator
# use & bit operator directly in the SQL query.
# This has the drawback of not using an index on the flags colum.
- "(#{table_name}.#{colmn} & #{flag_mapping[colmn][flag]} = #{enabled ? flag_mapping[colmn][flag] : 0})"
+ "(#{custom_table_name}.#{colmn} & #{flag_mapping[colmn][flag]} = #{enabled ? flag_mapping[colmn][flag] : 0})"
elsif flag_options[colmn][:flag_query_mode] == :in_list
# use IN() operator in the SQL query.
# This has the drawback of becoming a big query when you have lots of flags.
neg = enabled ? "" : "not "
- "(#{table_name}.#{colmn} #{neg}in (#{sql_in_for_flag(flag, colmn).join(',')}))"
+ "(#{custom_table_name}.#{colmn} #{neg}in (#{sql_in_for_flag(flag, colmn).join(',')}))"
else
raise NoSuchFlagQueryModeException
end
@@ -199,6 +219,12 @@ def sql_in_for_flag(flag, colmn)
num = 2 ** flag_mapping[flag_options[colmn][:column]].length
(1..num).select {|i| i & val == val}
end
+
+ def sql_set_for_flag(flag, colmn, enabled = true, custom_table_name = self.table_name)
+ check_flag(flag, colmn)
+
+ "#{custom_table_name}.#{colmn} = #{custom_table_name}.#{colmn} #{enabled ? "| " : "& ~" }#{flag_mapping[colmn][flag]}"
+ end
def is_valid_flag_key(flag_key)
flag_key > 0 && flag_key == flag_key.to_i
@@ -1,3 +1,3 @@
module FlagShihTzu
- VERSION = "0.2.3"
+ VERSION = "0.3.0"
end
@@ -45,6 +45,15 @@ class SpaceshipWith2CustomFlagsColumn < ActiveRecord::Base
has_flags({ 1 => :jeanlucpicard, 2 => :dajanatroj }, :column => 'commanders')
end
+class SpaceshipWith3CustomFlagsColumn < ActiveRecord::Base
+ self.table_name = 'spaceships_with_3_custom_flags_column'
+ include FlagShihTzu
+
+ has_flags({ 1 => :warpdrive, 2 => :hyperspace }, :column => 'engines')
+ has_flags({ 1 => :photon, 2 => :laser, 3 => :ion_cannon, 4 => :particle_beam }, :column => 'weapons')
+ has_flags({ 1 => :power, 2 => :anti_ax_routine }, :column => 'hal3000')
+end
+
class SpaceshipWithBitOperatorQueryMode < ActiveRecord::Base
self.table_name = 'spaceships'
include FlagShihTzu
@@ -117,6 +126,21 @@ def jeanluckpicard; end
end
end
+ def test_has_flags_should_raise_an_exception_when_flag_name_method_defined_by_flagshitzu_if_strict
+ assert_raises FlagShihTzu::DuplicateFlagColumnException do
+ eval(<<-EOF
+ class SpaceshipWithAlreadyUsedMethodByFlagshitzuStrict < ActiveRecord::Base
+ self.table_name = 'spaceships_with_2_custom_flags_column'
+ include FlagShihTzu
+
+ has_flags({ 1 => :jeanluckpicard }, :column => 'bits', :strict => true)
+ has_flags({ 1 => :jeanluckpicard }, :column => 'bits', :strict => true)
+ end
+ EOF
+ )
+ end
+ end
+
def test_has_flags_should_not_raise_an_exception_when_flag_name_method_defined_by_flagshitzu
assert_nothing_raised ArgumentError do
eval(<<-EOF
@@ -781,4 +805,20 @@ def test_should_define_bang_methods
spaceship.not_warpdrive!
assert !spaceship.warpdrive
end
+
+ def test_should_return_a_sql_set_method_for_flag
+ assert_equal "spaceships.flags = spaceships.flags | 1", Spaceship.send( :sql_set_for_flag, :warpdrive, 'flags', true)
+ assert_equal "spaceships.flags = spaceships.flags & ~1", Spaceship.send( :sql_set_for_flag, :warpdrive, 'flags', false)
+ end
+
+end
+
+class FlagShihTzuClassMethodsTest < Test::Unit::TestCase
+
+ def test_should_track_columns_used_by_FlagShihTzu
+ assert_equal Spaceship.flag_columns, ['flags']
+ assert_equal SpaceshipWith2CustomFlagsColumn.flag_columns, ['bits', 'commanders']
+ assert_equal SpaceshipWith3CustomFlagsColumn.flag_columns, ['engines', 'weapons', 'hal3000']
+ end
+
end
View
@@ -14,6 +14,12 @@
t.integer :commanders, :null => false, :default => 0
end
+ create_table :spaceships_with_3_custom_flags_column, :force => true do |t|
+ t.integer :engines, :null => false, :default => 0
+ t.integer :weapons, :null => false, :default => 0
+ t.integer :hal3000, :null => false, :default => 0
+ end
+
create_table :spaceships_without_flags_column, :force => true do |t|
end

0 comments on commit 2980ef3

Please sign in to comment.