-
-
Notifications
You must be signed in to change notification settings - Fork 638
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
264 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# this class is responsible of normalizing the hash of conditions | ||
# by exploding has_many through associations | ||
# when a condition is defined with an has_many thorugh association this is exploded in all its parts | ||
# TODO: it could identify STI and normalize it | ||
module CanCan | ||
module ModelAdapters | ||
class ConditionsNormalizer | ||
class << self | ||
def normalize(model_class, rules) | ||
rules.each { |rule| rule.conditions = normalize_conditions(model_class, rule.conditions) } | ||
end | ||
|
||
def normalize_conditions(model_class, conditions) | ||
return conditions unless conditions.is_a? Hash | ||
|
||
conditions.each_with_object({}) do |(key, value), result_hash| | ||
if value.is_a? Hash | ||
result_hash.merge!(calculate_result_hash(model_class, key, value)) | ||
else | ||
result_hash[key] = value | ||
end | ||
result_hash | ||
end | ||
end | ||
|
||
private | ||
|
||
def calculate_result_hash(model_class, key, value) | ||
reflection = model_class.reflect_on_association(key) | ||
unless reflection | ||
raise WrongAssociationName, "Association '#{key}' not defined in model '#{model_class.name}'" | ||
end | ||
|
||
if reflection.options[:through].present? | ||
key = reflection.options[:through] | ||
value = { reflection.source_reflection_name => value } | ||
reflection = model_class.reflect_on_association(key) | ||
end | ||
|
||
{ key => normalize_conditions(reflection.klass.name.constantize, value) } | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
require 'spec_helper' | ||
|
||
# integration tests for latest ActiveRecord version. | ||
RSpec.describe CanCan::ModelAdapters::ActiveRecord5Adapter do | ||
let(:ability) { double.extend(CanCan::Ability) } | ||
let(:users_table) { Post.table_name } | ||
let(:posts_table) { Post.table_name } | ||
let(:likes_table) { Like.table_name } | ||
before :each do | ||
connect_db | ||
ActiveRecord::Migration.verbose = false | ||
|
||
ActiveRecord::Schema.define do | ||
create_table(:users) do |t| | ||
t.string :name | ||
t.timestamps null: false | ||
end | ||
|
||
create_table(:posts) do |t| | ||
t.string :title | ||
t.boolean :published, default: true | ||
t.integer :user_id | ||
t.timestamps null: false | ||
end | ||
|
||
create_table(:likes) do |t| | ||
t.integer :post_id | ||
t.integer :user_id | ||
t.timestamps null: false | ||
end | ||
|
||
create_table(:editors) do |t| | ||
t.integer :post_id | ||
t.integer :user_id | ||
t.timestamps null: false | ||
end | ||
end | ||
|
||
class User < ActiveRecord::Base | ||
has_many :posts | ||
has_many :likes | ||
has_many :editors | ||
end | ||
|
||
class Post < ActiveRecord::Base | ||
belongs_to :user | ||
has_many :likes | ||
has_many :editors | ||
end | ||
|
||
class Like < ActiveRecord::Base | ||
belongs_to :user | ||
belongs_to :post | ||
end | ||
|
||
class Editor < ActiveRecord::Base | ||
belongs_to :user | ||
belongs_to :post | ||
end | ||
end | ||
|
||
before do | ||
@user1 = User.create! | ||
@user2 = User.create! | ||
@post1 = Post.create!(title: 'post1', user: @user1) | ||
@post2 = Post.create!(user: @user1, published: false) | ||
@post3 = Post.create!(user: @user2) | ||
@like1 = Like.create!(post: @post1, user: @user1) | ||
@like2 = Like.create!(post: @post1, user: @user2) | ||
@editor1 = Editor.create(user: @user1, post: @post2) | ||
ability.can :read, Post, user_id: @user1 | ||
ability.can :read, Post, editors: { user_id: @user1 } | ||
end | ||
|
||
describe 'preloading of associatons' do | ||
it 'preloads associations correctly' do | ||
posts = Post.accessible_by(ability).includes(likes: :user) | ||
expect(posts[0].association(:likes)).to be_loaded | ||
expect(posts[0].likes[0].association(:user)).to be_loaded | ||
end | ||
end | ||
|
||
describe 'filtering of results' do | ||
it 'adds the where clause correctly' do | ||
posts = Post.accessible_by(ability).where(published: true) | ||
expect(posts.length).to eq 1 | ||
end | ||
end | ||
|
||
if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0') | ||
describe 'selecting custom columns' do | ||
it 'extracts custom columns correctly' do | ||
posts = Post.accessible_by(ability).select('title as mytitle') | ||
expect(posts[0].mytitle).to eq 'post1' | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
require 'spec_helper' | ||
|
||
RSpec.describe CanCan::ModelAdapters::ConditionsNormalizer do | ||
before do | ||
connect_db | ||
ActiveRecord::Migration.verbose = false | ||
ActiveRecord::Schema.define do | ||
create_table(:articles) do |t| | ||
end | ||
|
||
create_table(:users) do |t| | ||
t.string :name | ||
end | ||
|
||
create_table(:comments) do |t| | ||
end | ||
|
||
create_table(:spread_comments) do |t| | ||
t.integer :article_id | ||
t.integer :comment_id | ||
end | ||
|
||
create_table(:legacy_mentions) do |t| | ||
t.integer :user_id | ||
t.integer :article_id | ||
end | ||
end | ||
|
||
class Article < ActiveRecord::Base | ||
has_many :spread_comments | ||
has_many :comments, through: :spread_comments | ||
has_many :mentions | ||
has_many :mentioned_users, through: :mentions, source: :user | ||
end | ||
|
||
class Comment < ActiveRecord::Base | ||
has_many :spread_comments | ||
has_many :articles, through: :spread_comments | ||
end | ||
|
||
class SpreadComment < ActiveRecord::Base | ||
belongs_to :comment | ||
belongs_to :article | ||
end | ||
|
||
class Mention < ActiveRecord::Base | ||
self.table_name = 'legacy_mentions' | ||
belongs_to :article | ||
belongs_to :user | ||
end | ||
|
||
class User < ActiveRecord::Base | ||
has_many :mentions | ||
has_many :mentioned_articles, through: :mentions, source: :article | ||
end | ||
end | ||
|
||
it 'simplifies has_many through associations' do | ||
rule = CanCan::Rule.new(true, :read, Comment, articles: { mentioned_users: { name: 'pippo' } }) | ||
CanCan::ModelAdapters::ConditionsNormalizer.normalize(Comment, [rule]) | ||
expect(rule.conditions).to eq(spread_comments: { article: { mentions: { user: { name: 'pippo' } } } }) | ||
end | ||
|
||
it 'normalizes the has_one through associations' do | ||
class Supplier < ActiveRecord::Base | ||
has_one :accountant | ||
has_one :account_history, through: :accountant | ||
end | ||
|
||
class Accountant < ActiveRecord::Base | ||
belongs_to :supplier | ||
has_one :account_history | ||
end | ||
|
||
class AccountHistory < ActiveRecord::Base | ||
belongs_to :accountant | ||
end | ||
|
||
rule = CanCan::Rule.new(true, :read, Supplier, account_history: { name: 'pippo' }) | ||
CanCan::ModelAdapters::ConditionsNormalizer.normalize(Supplier, [rule]) | ||
expect(rule.conditions).to eq(accountant: { account_history: { name: 'pippo' } }) | ||
end | ||
end |