I'm trying to define an ability:
# customer.rb
class Customer
include Mongoid::Document
has_many :accounts
end
class Account
include Mongoid::Document
belongs_to :site
end
# ability.rb
can :manage, Customer, accounts: { :site_id.in => user.managed_site_ids }
# customers_controller.rb
Customer.accessible_by(current_ability)
It's always returns empty result, and I am not sure, but I think because mongoid doesn't support join query. Do you know what's wrong, or other alternatives?
Related
I'm new to Rails 5 and I'm trying to model the following scenario using Devise and CanCanCan: a store application.
Relevant figures are: Admin, StoreManager, StoreOfficer, Customer, Technician.
Admin creates StoreManager.
StoreOfficer can create Customer's information to register new Customers.
StoreOfficer can see the complete list of all Customers with relative information
StoreOfficer can see the complete list of all Technicians with relative information
StoreManager can see the complete list of all StoreOfficers with relative information
StoreManager can enable StoreOfficers to use the system and edit their information
StoreManager can enable Customers created by StoreOfficers to use the system
StoreManager can see the complete list of all Customers with relative information
There are also some other paths, but it's possible for me to develop them as these presented cases are done.
Any help/tutorial please?
Thanks,
FZ
After trying some different things, I created the following satisfying schema, which I post here for others so that they can use something solid:
Class User<ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :userable, polymorphic: true
end
Then all the other roles classes similar to the following one:
class Customer < ApplicationRecord
belongs_to :technician
belongs_to :store_officer
has_one :user, as: :userable, dependent: :destroy
has_one :address, as: :addressable, dependent: :destroy
accepts_nested_attributes_for :user, :address
end
Accordingly, I created all the needed fields using migrations in the database.
Then I put in every controller the code before_action :authenticate_user!.
After populating the db a bit, I can now login with different users inserted using rake db:seed.
This is my routes.rb file:
Rails.application.routes.draw do
devise_for :users
resources :store_managers
resources :store_officers
resources :items
resources :technicians
resources :customers
resources :addresses
resources :users
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
What do you all think about this setup?
Now I want to introduce CanCanCan to my project. Please validate the following steps I'm going to do:
Run rails g cancan:ability
Put all the rules in the ability.rb file:
if user.admin?
can :manage, :all
elsif user.store_manager?
can :read, Customer
can :create, Customer
can :update, Item do |item|
item.try(:user) == user
end
can :destroy, Item do |item|
item.try(:user) == user
end
elsif user.store_officer?
can :read, Customer
end
.
.
.
Create the methods in user.rb model to check roles:
def customer?
self.userable_type == Customer
end
Put load_and_authorize_resource in every controller
Do you think this approach is the correct one? Or maybe I should put methods/definitions in other files?
Thanks,
FZ
I've seen a lot of many-to-many associations, but it seems that the common trend is that they end up using has_many :through relationships.
Let's say you have the following:
class User < ActiveRecord::Base
has_many :user_relationships
has_many :relations, :through => :user_relationships
end
class UserRelationship < ActiveRecord::Base
belongs_to :user
belongs_to :related_user, class_name: "User"
end
Given this type of relationship you end up setting the relationship up as follows:
user1.relations << user2
user2.relations << user1
Or this:
user1.user_relationships.build(related_user_id: 2)
user2.user_relationships.build(related_user_id: 1)
Resulting in rows in the join table looking like this:
user_id | related_user_id
1 | 2
2 | 1
So that when setting up the relation such as the above, you can see that the following can be accomplished
user1.relations.include? user2 = true
user2.relations.include? user1 = true
My question is: Is there a way to accomplish the above, or at least something similar to the above in speed, in Rails WITHOUT having to create 2 rows for every single two-way relationship and maintain the ability to see the relationship from both ends in an efficient manner, reducing the space complexity of creating this relationship by half...
Apologies if this is a noobie question, but I'm new to Rails, just starting to get the hang of things. It's easy to find out how to set these up, but I find it much harder to find out how to actually implement them in an efficient manner
Here's (roughly) what I've done. I'm going to skip a ton of the detail, but am happy to share more if it's helpful. This may be heinous, so I'm curious about others' thoughts.
Essentially, I have a Relationship model something like:
module ActsAsRelatingTo
class Relationship < ActiveRecord::Base
validates :owner_id, presence: true
validates :owner_type, presence: true
validates :in_relation_to_id, presence: true
validates :in_relation_to_type, presence: true
belongs_to :owner, polymorphic: true
belongs_to :in_relation_to, polymorphic: true
acts_as_taggable
acts_as_taggable_on :roles
end
end
Then, I've created a acts_as_relating_to module (again, glossing over the details) that includes stuff like:
module ActsAsRelatingTo
def acts_as_relating_to(*classes_array)
# re-opens the class at run time so I can do things like add
# new instance methods on-the-fly
class_eval do
before_destroy :tell_to_unrelate
has_many :owned_relationships,
as: :owner,
class_name: "ActsAsRelatingTo::Relationship",
dependent: :destroy
has_many :referencing_relationships,
as: :in_relation_to,
class_name: "ActsAsRelatingTo::Relationship",
dependent: :destroy
# iterates through arguments passed in 'acts_as_relating_to' call
# in the Person model, below.
classes_array.each do |class_sym|
# This is a method created on-the-fly. So, when I call
# (in the Person model, below) 'acts_as_relating_to :people',
# then I get a method on each instance of 'Person' called
# 'people_that_relate_to_me. You can also create class methods
# using `define_singleton_method`.
#
# The reason for doing all of this via the define_method
# is that it lets me create any number of 'things_i_relate_to'
# methods on any class descending from ActiveRecord::Base
# (more on this in a bit). Which, if I understand it
# correctly, is how a lot of the ActiveRecord functionality gets
# into a model in the first place.
define_method(class_sym.to_s + "_that_relate_to_me") do |options={}|
... some stuff
end
# Same as above, but know I'm defining a method called
# 'people_i_relate_to'
define_method(class_sym.to_s+"_i_relate_to") do |options={}|
... some more stuff
end
# I can also create static methods and incorporate them in the
# 'Person' class. I just define them in modules (such as
# ActsAsRelatingTo::InstanceMethods and ActsAsRelatingTo::ClassMethods)
# and then either 'include' (for instance methods) or 'extend'
# (for class methods) them.
include InstanceMethods
extend ClassMethods
end
end
end
end
# Here, I'm telling ActiveRecord::Base to 'extend' this module.
# That makes the 'acts_as_relating_to' method available in
# any class that descends from ActiveRecord::Base.
ActiveRecord::Base.extend ActsAsRelatingTo
Then, I can do something like:
class Person < ActiveRecord::Base
# Here, I am calling the method that I defined above, passing in
# :people, :organizations, and :programs. This is exactly the
# sort of thing you do all the time when you say something like
# 'has_one :foo', or 'belongs_to :bar'.
acts_as_relating_to :people, :organizations, :programs
# Here, I am calling a method I have that builds on acts_as_relating_to,
# but which I did not show, that creates administrative methods on
# the person so that I can say stuff like 'person.administrate organization'.
# Or, 'organization.administrators'.
acts_as_administering :organizations, :programs
...
end
So, if I have person_1 and person_2 and I have one relationship record where the owner is person_1, and the in_relation_to is person_2, then I can say person_1.people_i_relate_to and get back person_2. Or, I can say, person_2.people_that_relate_to_me and get back person_1.
I've also go an acts_as_administering module which builds on the acts_as_relating_to module and lets me do stuff like person_1.administered_organizations and person_1.administered_programs.
I've probably rattled on too long. Anyway, if it's interesting, I can say more.
Cheers!
Here's a finder method that I am writing in my Objective class:
def followed_objectives_for_user(user)
Objective.followed_by_user(user)
.joins(:resource_objective_links)
.where('resource_objective_links.resource_id = ? ', self)
end
It feels messy to have the knowledge of the ResourceObjectiveLink's resource_id column name known to the Objective class like this. I tried making a scope on the ResourceObjectiveLink class like this, but it can't find it when I chain it in the followed_objectives_for_user method in Objective:
scope :for_resource, ->(resource) { where(resource_id: resource.id) }
What is the correct way to hide this information?
Why cannot you just use ActiveRecord relations? Than just
User has_many :resource_objective_links
User has_many :objectives, through: :resource_objective_link
Objective has_many :resource_objective_links
Objective has_many :users, through: :resource_objective_links
ResourceObjectiveLink belongs_to :user
ResourceObjectiveLink belongs_to :objective
# And now you can use:
# user = User.find(some_user_id)
# user.objectives
# => will perform database join and return all Objectives for ResourceObjectiveLink with correct user.id
I am using Ruby on Rails 3.2.2 and MySQL. I would like to "merge" the result of more than one ActiveRecord::Associations and one or more scope methods. That is, I have:
class User < ActiveRecord::Base
has_many :a_user_relationships, :foreign_key => 'a_key'
has_many :b_user_relationships, :foreign_key => 'b_key'
has_many :a_articles, :through => :a_user_article_associations # Returns objects kind of 'Article'
has_many :b_articles, :through => :b_user_article_associations # Returns objects kind of 'Article'
end
class Article < ActiveRecord::Base
# Note: This is a scope method.
def self.public
where(:status => 'public')
end
end
Given the above code I would like to run some method (as-like the following) so to retieve all public "a" and "b" user articles by executing as few as possible database queries:
#user.all_articles
class User < ActiveRecord::Base
# Note: Code in the following method is incorrect, but maybe it helps
# understanding what I mean.
def all_articles
self.(a_articles & b_articles).public
(self.a_articles.public & self.b_articles.public)
end
end
Is it possible?
Since you have two separate associations, you'll need at least two queries.
If you always want articles from both here, is there anything stopping you from having a simple "articles" association in addition to or instead of these two separate ones? That way, you could retrieve your articles with self.articles.public.
I have a group model. I want to query a group to see if user exists in it. Typically this is much easier using embedded documents but unfortunately I cannot do that in this case. In an embedded scenario I would do the following. How can I do this query in a referenced scenario.
Note:** I do not want to use habtm relationships.
Query
Matter.where(:'matter_counsels._id' => the_id)
Classes
class Matter
include Mongoid::Document
# Relationships
has_many :matter_counsels # subclass of MatterRelationship
has_many :matter_clients # subclass of MatterRelationship
has_many :matter_opposing_parties # subclass of MatterRelationship
has_many :matter_related_parties # subclass of MatterRelationship
end
class MatterRelationship
include Mongoid::Document
belongs_to :matter
end
I have to admit the example I had above wasn't clear. I updated the problem along with my solution.
class Matter
include Mongoid::Document
# Relationships
has_many :matter_counsels # subclass of MatterRelationship
has_many :matter_clients # subclass of MatterRelationship
has_many :matter_opposing_parties # subclass of MatterRelationship
has_many :matter_related_parties # subclass of MatterRelationship
# Finder Scopes
class << self
# finds a type of matter relationship by user
def for_user(user,type)
user_id = user.id.to_s
matter_ids = MatterRelationship.where(contact_id: user_id, _type: type).collect{ |i| i.matter_id.to_s }
where(:_id.in => matter_ids)
end
end
end