How to define CanCan abilities with model associations - ruby-on-rails

I'm using CanCan to define abilities in my app.
Question is how to define ability of a model that belongs to the User through an association.
For example this works because Campaign has an affiliate_id field
can :manage, Campaign, :affiliate_id => affiliate.id
However, Affiliates have Subscriptions and Messages through Keywords. Keywords belong to a Campaign.
All associations are working fine. However I'm not sure how to limit only Subscriptions and Messages to the current_user.
These are my current associations
# affiliate.rb
has_many :keywords, :through => :campaigns
has_many :campaigns
# campaign.rb
belongs_to :affiliate
has_many :keywords
has_many :subscriptions, :through => :keywords
has_many :messages, :through => :subscriptions
# keyword.rb
has_many :subscriptions
has_many :messages
belongs_to :campaign
# message.rb
belongs_to :user
belongs_to :campaign
belongs_to :subscription
belongs_to :keyword
# subscription.rb
belongs_to :campaign
belongs_to :affiliate
belongs_to :keyword
has_many :messages
Also, (bonus question), how can I show all objects if the user is admin? I have something like this but It's not retrieving all objects.
if affiliate.status == "admin"
can :manage, :all
end

Related

How to access two polymorphic associations with one query?

In my Rails 6 app I have these models:
class Account < ApplicationRecord
has_many :carriers
has_many :clients
# Unfortunately, these two don't work together. I have to uncomment one of them to get the other one to work:
has_many :people, :through => :carriers # works but omits clients
has_many :people, :through => :clients # works but omits carriers
end
class Carrier < ApplicationRecord
belongs_to :account
has_many :people, :as => :personalizable
end
class Client < ApplicationRecord
belongs_to :account
has_many :people, :as => :personalizable
end
class Person < ApplicationRecord
belongs_to :personalizable, :polymorphic => true
end
How can I access an account's carriers and clients in one query?
I would love to do something like account.people to show all the account's people but haven't found a way to achieve that yet.
How can it be done?
You cannot use same method name for two associations instead you can rename it as carrier_people and client_people and eager load both.
class Account < ApplicationRecord
has_many :carriers
has_many :clients
has_many :carrier_people, :through => :carriers, source: :people # works but omits clients
has_many :client_people, :through => :clients, source: :people # works but omits carriers
end
You can eager load like this.
Account.includes(:carrier_people, :client_people)

Is it possible to have an association with the same model, but some records have another association to go through?

Kind of a difficult concept to put into a title, but here is the use case:
I have users, the users can either belong to a partner or a customer. A partner can have many customers, but all users have a partner either directly or indirectly. Is there any way to create an association that would allow for partner access on any user regardless of whether or not they are directly related to it?
This is my current setup:
class User < ActiveRecord::Base
has_one :partner, through: :user_join, :source => :userable, :source_type => "Licensee"
has_one :customer, through: :user_join, :source => :userable, :source_type => "Customer"
end
class Partner < ActiveRecord::Base
has_many :customers
has_many :user_joins, as: :userable
has_many :users, through: :user_joins
end
class Customer < ActiveRecord::Base
belongs_to :partner
has_many :user_joins, as: :userable
has_many :users, through: :user_joins
end

Rails association - multiple model abilities

I have the following where a user can create a newsletter and can also subscribe to a newsletter. Is there a better way to distinguish between these two abilities with associations or can I put the ownership in the subscription model as suggested by: multiple has_many associations in Rails. With this solution how can I code the newsletter model to get the newsletter owner and newsletter subscribers.
Newsletter Model
# fields: id, user_id
has_many :subscriptions
has_many :users, through: :subscriptions
belongs_to :user *i.e. created by a single user*
Subscription Model
# fields: user_id, newsletter_id
belongs_to :newsletter
belongs_to :user
User Model
# fields: id
has_many :subscriptions
has_many :newsletters, through: :subscriptions
has_many :newsletters *i.e. created many newsletters*
has_many :created_newsletters, :through => :subscriptions, *old answer*
:source => :newsletter,
:conditions => ["newsletter.creator = ?", true]
Newsletter Model
has_many :subscriptions
has_many :subscribers, through: :subscriptions, source: :user
belongs_to :creator, class_name: "User", foreign_key: "user_id"
Subscription Model
# fields: user_id, newsletter_id
belongs_to :newsletter
belongs_to :user
User Model
# fields: id
has_many :subscriptions
has_many :subscribed_newsletters, through: :subscriptions, source: :newsletter
has_many :created_newsletters, class_name: "Newsletter", foreign_key: "user_id"
Now you can get subscribers and creator by doing
newsletter.subscribers
newsletter.creator

Association rules in rails

I have a User model, a Listing model and an Order model. A user can either place an order or publish a listing which others can place an order for. Thus, a User can be customer as well as supplier.
My Order model has listing_id, from_id and to_id.
My question is, how can I set up associations between these models ? I read the rails guide on associations but the example there were dealing with separate customer and supplier models.
class User < ActiveRecord::Base
has_many :listings, :foreign_key => :supplier_id, :inverse_of => :supplier
has_many :orders, :foreign_key => :customer_id, :inverse_of => :customer
end
class Listing < ActiveRecord::Base
belongs_to :supplier, :class_name => 'User'
belongs_to :order
end
class Order < ActiveRecord::Base
belongs_to :customer, :class_name => 'User'
has_many :listings
end

How do you model "Likes" in rails?

I have 3 models: User, Object, Likes
Currently, I have the model: a user has many Objects. How do I go about modeling:
1) A user can like many objects
2) an Object can have many likes (from different users)
So I want to be able to do something like this:
User.likes = list of objects liked by a user
Objects.liked_by = list of Users liked by object
The model below is definitely wrong...
class User < ActiveRecord::Base
has_many :objects
has_many :objects, :through => :likes
end
class Likes < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Objects < ActiveRecord::Base
belongs_to :users
has_many :users, :through => :likes
end
To elaborate further on my comment to Brandon Tilley's answer, I would suggest the following:
class User < ActiveRecord::Base
# your original association
has_many :things
# the like associations
has_many :likes
has_many :liked_things, :through => :likes, :source => :thing
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :thing
end
class Thing < ActiveRecord::Base
# your original association
belongs_to :user
# the like associations
has_many :likes
has_many :liking_users, :through => :likes, :source => :user
end
You are close; to use a :through, relation, you first must set up the relationship you're going through:
class User < ActiveRecord::Base
has_many :likes
has_many :objects, :through => :likes
end
class Likes < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Objects < ActiveRecord::Base
has_many :likes
has_many :users, :through => :likes
end
Note that Objects should has_many :likes, so that the foreign key is in the right place. (Also, you should probably use the singular form Like and Object for your models.)
Here is a simple method to achieve this. Basically, you can create as many relationships as needed as long as you specify the proper class name using the :class_name option. However, it is not always a good idea, so make sure only one is used during any given request, to avoid additional queries.
class User < ActiveRecord::Base
has_many :likes, :include => :obj
has_many :objs
has_many :liked, :through => :likes, :class_name => 'Obj'
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :obj
end
class Obj < ActiveRecord::Base
belongs_to :user
has_many :likes, :include => :user
has_many :users, :through => :likes
# having both belongs to and has many for users may be confusing
# so it's better to use a different name
has_many :liked_by, :through => :likes, :class_name => 'User'
end
u = User.find(1)
u.objs # all objects created by u
u.liked # all objects liked by u
u.likes # all likes
u.likes.collect(&:obj) # all objects liked by u
o = Obj.find(1)
o.user # creator
o.users # users who liked o
o.liked_by # users who liked o. same as o.users
o.likes # all likes for o
o.likes.collect(&:user)
Models & associations as per naming conventions of rails modeling
class User < ActiveRecord::Base
has_many :likes
has_many :objects, :through => :likes
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Object < ActiveRecord::Base
belongs_to :user
has_many :likes
has_many :users, :through => :likes
end
Also, you can use of already built-in gems like acts-as-taggable-on to have same functionality without code :)

Resources