diff --git a/lib/rolify/adapters/active_record/role_adapter.rb b/lib/rolify/adapters/active_record/role_adapter.rb index a1f01fb3..f3ab4b3a 100644 --- a/lib/rolify/adapters/active_record/role_adapter.rb +++ b/lib/rolify/adapters/active_record/role_adapter.rb @@ -104,15 +104,16 @@ def build_conditions(relation, args) end def build_query(role, resource = nil) - return [ "#{role_table}.name = ?", [ role ] ] if resource == :any - query = "((#{role_table}.name = ?) AND (#{role_table}.resource_type IS NULL) AND (#{role_table}.resource_id IS NULL))" + role = [role].flatten + return [ "#{role_table}.name IN (?)", [ role ] ] if resource == :any + query = "((#{role_table}.name IN (?)) AND (#{role_table}.resource_type IS NULL) AND (#{role_table}.resource_id IS NULL))" values = [ role ] if resource query.insert(0, "(") - query += " OR ((#{role_table}.name = ?) AND (#{role_table}.resource_type = ?) AND (#{role_table}.resource_id IS NULL))" + query += " OR ((#{role_table}.name IN (?)) AND (#{role_table}.resource_type = ?) AND (#{role_table}.resource_id IS NULL))" values << role << (resource.is_a?(Class) ? resource.to_s : resource.class.name) if !resource.is_a? Class - query += " OR ((#{role_table}.name = ?) AND (#{role_table}.resource_type = ?) AND (#{role_table}.resource_id = ?))" + query += " OR ((#{role_table}.name IN (?)) AND (#{role_table}.resource_type = ?) AND (#{role_table}.resource_id = ?))" values << role << resource.class.name << resource.id end query += ")" diff --git a/lib/rolify/finders.rb b/lib/rolify/finders.rb index c7306ac4..b9c1e802 100644 --- a/lib/rolify/finders.rb +++ b/lib/rolify/finders.rb @@ -14,36 +14,53 @@ def without_role(role_name, resource = nil) end def with_all_roles(*args) - users = [] - parse_args(args, users) do |users_to_add| - users = users_to_add if users.empty? - users &= users_to_add - return [] if users.empty? - end - users + intersect_ars(parse_args(args, :split)).uniq end def with_any_role(*args) - users = [] - parse_args(args, users) do |users_to_add| - users += users_to_add - end - users.uniq + union_ars(parse_args(args, :join)).uniq end end - + private - - def parse_args(args, users, &block) - args.each do |arg| - if arg.is_a? Hash - users_to_add = self.with_role(arg[:name], arg[:resource]) - elsif arg.is_a?(String) || arg.is_a?(Symbol) - users_to_add = self.with_role(arg) - else - raise ArgumentError, "Invalid argument type: only hash or string or symbol allowed" + + def parse_args(args, mode, &block) + normalize_args(args, mode).map do |arg| + self.with_role(arg[:name], arg[:resource]).tap do |users_to_add| + block.call(users_to_add) if block end - block.call(users_to_add) end end -end \ No newline at end of file + + # In: [:a, "b", { name: :c, resource: :d }] + # Out: [{ name: [:a, "b"] }, { name: :c, resource: :d }] + def normalize_args(args, mode) + groups = args.group_by(&:class) + unless groups.keys.all? { |type| [Hash, Symbol, String].include?(type) } + raise ArgumentError, "Invalid argument type: only hash or string or symbol allowed" + end + + normalized = [groups[Hash]] + sym_str_args = [groups[Symbol], groups[String]].flatten.compact + case mode + when :join + normalized += [{ name: sym_str_args }] unless sym_str_args.empty? + when :split + normalized += sym_str_args.map { |arg| { name: arg } } + end + + normalized.flatten.compact + end + + + def intersect_ars(ars) + query = ars.map(&:to_sql).join(" INTERSECT ") + from("(#{query}) AS #{table_name}") + end + + # http://stackoverflow.com/a/16868735/1202488 + def union_ars(ars) + query = ars.map(&:to_sql).join(" UNION ") + from("(#{query}) AS #{table_name}") + end +end diff --git a/spec/rolify/shared_examples/shared_examples_for_finders.rb b/spec/rolify/shared_examples/shared_examples_for_finders.rb index 216d12eb..2b39b113 100644 --- a/spec/rolify/shared_examples/shared_examples_for_finders.rb +++ b/spec/rolify/shared_examples/shared_examples_for_finders.rb @@ -115,7 +115,7 @@ end end end - + describe ".with_all_roles" do it { should respond_to(:with_all_roles) } @@ -129,6 +129,11 @@ it { subject.with_all_roles({ :name => "visitor".send(param_method), :resource => Forum.last }, { :name => "moderator".send(param_method), :resource => Group }).should eq([ root ]) } it { subject.with_all_roles({ :name => "visitor".send(param_method), :resource => Group.first }, { :name => "moderator".send(param_method), :resource => Forum }).should eq([ modo ]) } it { subject.with_all_roles({ :name => "visitor".send(param_method), :resource => :any }, { :name => "moderator".send(param_method), :resource => :any }).should =~ [ root, modo ] } + + it 'should return an AR relation' do + subject.with_all_roles("admin".send(param_method), :staff).should be_a(ActiveRecord::Relation) + subject.with_all_roles({ :name => "visitor".send(param_method), :resource => :any }, { :name => "moderator".send(param_method), :resource => :any }).should be_a(ActiveRecord::Relation) + end end describe ".with_any_role" do @@ -144,6 +149,11 @@ it { subject.with_any_role({ :name => "visitor".send(param_method), :resource => Forum.last }, { :name => "moderator".send(param_method), :resource => Group }).should =~ [ root, visitor ] } it { subject.with_any_role({ :name => "visitor".send(param_method), :resource => Group.first }, { :name => "moderator".send(param_method), :resource => Forum }).should eq([ modo ]) } it { subject.with_any_role({ :name => "visitor".send(param_method), :resource => :any }, { :name => "moderator".send(param_method), :resource => :any }).should =~ [ root, modo, visitor ] } + + it 'should return an AR relation' do + subject.with_any_role("admin".send(param_method), :staff, { :name => "moderator".send(param_method), :resource => Group }).should be_a(ActiveRecord::Relation) + subject.with_any_role({ :name => "visitor".send(param_method), :resource => :any }, { :name => "moderator".send(param_method), :resource => :any }).should be_a(ActiveRecord::Relation) + end end end -end \ No newline at end of file +end