Skip to content

Commit

Permalink
Implemented chained_flags_with, which allows optimizing the bit search
Browse files Browse the repository at this point in the history
When you chain multiple named scope generated by FlagShihTzu,
generated ActiveRecord query would look like this.

  Spaceship.warpdrive.shields.not_electrolytes
  # => (spaceships.flags in (1,3,5,7)) AND (spaceships.flags in (2,3,6,7)) AND (spaceships.flags not in (4,5,6,7))

With the chained_flags_with introduced with this patch, you can write
the same query in an optimized search.

  Spaceship.chained_flags_with("flags", :warpdrive, :shields, :not_electrolytes)
  # => (spaceships.flags in (3))

You can also name it with the named scope:

  scope :listed, chained_flags_with("flags", :warpdrive, :shields, :not_electrolytes)
  • Loading branch information
miyagawa committed Jun 25, 2012
1 parent 1d4acea commit b56b9ac
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 0 deletions.
27 changes: 27 additions & 0 deletions lib/flag_shih_tzu.rb
Expand Up @@ -93,8 +93,35 @@ def check_flag(flag, colmn)
raise ArgumentError, "Invalid flag '#{flag}'" if flag_mapping[colmn].nil? || !flag_mapping[colmn].include?(flag)
end

def chained_flags_with(*args)
where(chained_flags_condition(*args))
end

def chained_flags_condition(colmn, *args)
"(#{self.table_name}.#{colmn} in (#{chained_flags_values(colmn, *args).join(',')}))"
end

private

def chained_flags_values(colmn, *args)
val = (1..(2 ** flag_mapping[flag_options[colmn][:column]].length)).to_a
args.each do |flag|
neg = false
if flag.match /^not_/
neg = true
flag = flag.to_s.sub(/^not_/, '').to_sym
end
check_flag(flag, colmn)
flag_values = sql_in_for_flag(flag, colmn)
if neg
val = val - flag_values
else
val = val & flag_values
end
end
val
end

def parse_options(*args)
options = args.shift
if args.size >= 1
Expand Down
27 changes: 27 additions & 0 deletions test/flag_shih_tzu_test.rb
Expand Up @@ -241,6 +241,33 @@ def test_should_return_the_correct_number_of_items_from_a_named_scope
assert_equal 0, Spaceship.not_warpdrive.not_shields.count
end

def test_should_return_the_correct_condition_with_chained_flags
assert_equal "(spaceships.flags in (3,7))", Spaceship.chained_flags_condition("flags", :warpdrive, :shields)
assert_equal "(spaceships.flags in (7))", Spaceship.chained_flags_condition("flags", :warpdrive, :shields, :electrolytes)
assert_equal "(spaceships.flags in (2,6))", Spaceship.chained_flags_condition("flags", :not_warpdrive, :shields)
end

def test_should_return_the_correct_number_of_items_with_chained_flags_with
spaceship = Spaceship.new
spaceship.enable_flag(:warpdrive)
spaceship.enable_flag(:shields)
spaceship.save!
spaceship.reload
spaceship_2 = Spaceship.new
spaceship_2.enable_flag(:warpdrive)
spaceship_2.save!
spaceship_2.reload
spaceship_3 = Spaceship.new
spaceship_3.enable_flag(:shields)
spaceship_3.save!
spaceship_3.reload
assert_equal 2, Spaceship.chained_flags_with("flags", :warpdrive).count
assert_equal 1, Spaceship.chained_flags_with("flags", :warpdrive, :shields).count
assert_equal 1, Spaceship.chained_flags_with("flags", :warpdrive, :not_shields).count
assert_equal 0, Spaceship.chained_flags_with("flags", :not_warpdrive, :shields, :electrolytes).count
assert_equal 1, Spaceship.chained_flags_with("flags", :not_warpdrive, :shields, :not_electrolytes).count
end

def test_should_not_define_named_scopes_if_not_wanted
assert !SpaceshipWithoutNamedScopes.respond_to?(:warpdrive)
assert !SpaceshipWithoutNamedScopesOldStyle.respond_to?(:warpdrive)
Expand Down

1 comment on commit b56b9ac

@pboling
Copy link

@pboling pboling commented on b56b9ac Nov 6, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please create a pull request for this! Looks awesome!

Please sign in to comment.