I have a class that looks something like this:
class User < ActiveRecord:Base
has_many :users_companies
has_many :companies, :through => :users_companies
end
For plain users, I'd like user.companies to refer to the standard association method, but when a user is an admin, I want User.all (i.e., admins have access to all companies). The simplest way I can think of to implement this (and what I've always done in the past) is use a scope on the Company class, such as:
scope :accessible_by, lambda { |user| ... }
The only problem is that this just doesn't feel right. Instead of writing a controller action that includes:
#companies = Company.accessible_by(current_user)
I'd feel more comfortable writing
#companies = current_user.companies
Is there a good way to override the User#companies method to accommodate this kind of behavior? Or, should I be happy with using a scope on Company?
I'm wrestling with a similar problem. The only acceptable solution I can devise is an association extension, which overrides the query for admin users and passes normal users' queries, unmolested.
# this works for me in rails 3.1
class User < ActiveRecord:Base
has_many :users_companies
has_many :companies, :through => :users_companies do
def visible
if proxy_association.owner.admin?
UsersCompany.scoped
else
self
end
end
end
end
User.where(:admin => true).first.companies.visible == UsersCompany.all
I'm fairly new to Rails, but this is an interesting question so I figured I'd toss in my two cents. It seems that you should be able to extend your association in User with a companies method that checks self.is_admin? (or similar) and returns what you need. See http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many#461-User-a-block-to-extend-your-associations
Nice question. I was wondering if something like the following is an option you would consider
class User < ActiveRecord:Base
has_many :users_companies
has_many :companies, :through => :users_companies
def viewable_companies
admin? ? Company.all : self.companies
end
end
I know the naming is horrible but, you know, naming things is serious stuff :)
Related
I am trying to add a condition to a has many through association without luck. This is the association in my video model:
has_many :voted_users, :through => :video_votes, :source => :user
I want to only get the voted_users whose video_votes have a value equal to 1 for that video. How would I do this?
I would suggest creating a model method within the video model class
Something like:
def users_with_one_vote
self.voted_users, :conditions => ['value = ?', 1]
end
Then in the controller use video.users_with_one_vote
Then testing is easier too.
Any chance you can change that column name from 'value'. Might give some issues (reserved?).
I'd do this in 2 stages:
First, I'd define the has_many :through relationship between the models without any conditions.
Second, I'd add a 'scope' that defines a where condition.
Specifically, I'd do something like:
class User < ActiveRecord::Base
has_many :video_votes
has_many :votes, :through=>:video_votes
def self.voted_users
self.video_votes.voted
end
end
class VideoVote
def self.voted
where("value = ?", 1)
end
end
class Video
has_many :video_votes
has_many :users, :through=>:video_votes
end
Then you could get the users that have voted using:
VideoVote.voted.collect(&:user).uniq
which I believe would return an array of all users who had voted. This isn't the exact code you'd use -- they're just snippets -- but the idea is the same.
Would
has_many :voted_users, :through => :video_votes, :source => :user, :conditions => ['users.votes = ?', 1]
Do the trick?
I found that defining this method in my model works:
def upvoted_users
self.voted_users.where("value = 1")
end
and then calling #video.upvoted_users does the trick.
The best way to do this without messing with the relations is by crafting a more complex query. Relations is not the best thing to use for this particular problem. Please understand that relations is more a "way of data definition" then a way of "bussiness rules definition".
Bussiness logic or bussiness rules must be defined on a more specifically layer.
My suggestion for your problem is to create a method to search for users who voted on your video only once. something like:
class Video < ActiveRecord::Base
def voted_once()
User.joins(:video_votes).where("video_votes.value == 1 AND video_votes.video_id == ?", this.id)
end
Rails is magical for many things, but complex queries still have to be done in a "SQL" way of thinking. Don't let the illusional object oriented metaphor blind you
As long as we are throwing around ideas, how about using association extensions.
class VideoVote
scope :upvotes, where(:value => 1)
end
class Video
has_many :voted_users, :through => :video_votes, :source => :user do
def upvoted
scoped & VideoVote.upvotes
end
end
end
Then you feel good about making a call with absolutely no arguments AND you technically didn't add another method to your Video model (it's on the association, right?)
#video.voted_users.upvoted
I have the following two models, User..
class User < ActiveRecord::Base
has_and_belongs_to_many :sites
end
.. and Site:
class Site< ActiveRecord::Base
has_and_belongs_to_many :users
end
Up to this point its fine. It works and it's pretty simple.
Now I want to introduce "primary user" to the Site. I add "primary_user_id" to the Site, and trying to add a second association:
class Site< ActiveRecord::Base
has_and_belongs_to_many :user
# my new association that doesn't work...
has_one :primary_user, :class_name => "User", :conditions => ['id = ?', '{self.primary_user_id}']
end
It doesn't like it... Now I know that I can fake this by just adding a method "primary_user" to the site and this will work, but my question is whether it is possible to user ActiveRecord associations and how?
has_and_belongs_to_many is tricky and most people have moved away from it and use has_many through => model.
btw - 'Up to this point its fine. It works and it's pretty simple.' is how all things start off. How they perform when you 'really' start to use them is what counts and for that reason you'll probably find has_many through easier to work with.
These links will help:
http://paulbarry.com/articles/2007/10/24/has_many-through-checkboxes
http://thoughtsincomputation.com/posts/checkboxes-with-has_many-through
http://my.opera.com/durrantm/blog/2011/07/24/rails-simple-form-with-has-many-through-hmt-relationship
https://github.com/romanvbabenko/nested_has_many_through (nesting gem).
I have looked through the Ruby on Rails guides and I can't seem to figure out how to prevent someone from deleting a Parent record if it has Children. For example. If my database has CUSTOMERS and each customer can have multiple ORDERS, I want to prevent someone from deleting a customer if it has any orders in the database. They should only be able to delete a customer if it has no orders.
Is there a way when defining the association between models to enforce this behavior?
class Customer < ActiveRecord::Base
has_many :orders, :dependent => :restrict # raises ActiveRecord::DeleteRestrictionError
Edit: as of Rails 4.1, :restrict is not a valid option, and instead you should use either :restrict_with_error or :restrict_with_exception
Eg.:
class Customer < ActiveRecord::Base
has_many :orders, :dependent => :restrict_with_error
You could do this in a callback:
class Customer < ActiveRecord::Base
has_many :orders
before_destroy :check_for_orders
private
def check_for_orders
if orders.count > 0
errors.add_to_base("cannot delete customer while orders exist")
return false
end
end
end
EDIT
see this answer for a better way to do this.
Try using filters to hook in custom code during request processing.
One possibility would be to avoid providing your users a link to deletion in this scenario.
link_to_unless !#customer.orders.empty?
Another way would be to handle this in your controller:
if !#customer.orders.empty?
flash[:notice] = "Cannot delete a customer with orders"
render :action => :some_action
end
Or, as Joe suggests, before_filters could work well here and would probably be a much more DRY way of doing this, especially if you want this type of behavior for more models than just Customer.
I'm tempted to say yes.
A contrived example, using has_many :through and polymorphs:
class Person < ActiveRecord::Base
has_many :clubs, :through => :memberships
has_many :gyms, :through => :memberships
end
class Membership < ActiveRecord::Base
belongs_to :member, :polymorphic => true
end
class Club < ActiveRecord::Base
has_many :people, :through => :memberships
has_many :memberships, :as => :member
end
etc.
Leaving aside, for the moment, the question of whether a Gym is a Club, or any other design flaws.
To add a User to a Club, it's tempting to be RESTful and POST a person_id and a club_id to MembersController, like so:
form_for club_members_path(#club, :person_id => person.id) ...
In this scenario, when we decide to do:
form_for gym_members_path(#gym, :person_id => person.id) ...
We would need to make MembersController decide whether the parent resource is a Club or a Gym, and act accordingly. One non-DRY solution:
class MembersController < ApplicationController
before_filter :find_parent
...
private
def find_parent
#parent = Gym.find(params[:gym_id]) if params[:gym_id]
#parent = Club.find(params[:club_id]) if params[:club_id]
end
end
Shockingly awful if you do it more than once.
Also, it's predicated on the concept that joining a Club and joining a Gym are roughly the same. Or at least, Gym#add_member and Club#add_member will behave in a more or less parallel manner. But we have to assume that Gyms and Clubs might have different reasons for rejecting an application for membership. MembersController would need to handle flash messages and redirects for two or more error states.
There are solutions in the wild. James Golick's awesome ResourceController has a way of dealing with parent_type, parent_object, etc. Revolution On Rails has a nice solution for DRYing up multiple polymorphic controllers by adding some methods to ApplicationController. And of course, ActionController has #polymorhpic_url for simpler cases like Blog#posts and Article#posts, etc.
All this leaves me wondering, is it really worth putting all that pressure on MembersController at all? Polymorphism is handled pretty well in Rails, but my feeling is that using conditionals (if/unless/case) is a clear indication that you don't know what type you're dealing with. Metaprogramming helps, but only when the types have similar behavior. Both seem to point to the need for a design review.
I'd love to hear your thoughts on this. Is it better to be DRY in this scenario, or to know exactly what parent type you have? Am I being neurotic here?
A user has_many :donations, a project has_many :donations, and a donation belongs_to :user and belongs_to :project.
I'm looking for a sensible way to extract the projects associated with a user (through donations) into an array.
I'm currently doing:
def index
#user = User.find params[:user_id]
#projects = []
#user.donations.each do |donation|
#projects << donation.project
end
end
I feel like I'm missing something obvious, as this seems lame. Is there a better way to do this?
Edit
I accidentally simplified this too far. A user can also be associated with a project through other models, so #projects = #user.projects isn't going to do what I need it to.
class User < AR::Base
has_many :donations
has_many :projects, :through => :donations
…
end
#user.projects
should work.
For gathering many association collections see my previous answer. You will need to adapt it to use the through associations (just treat them as normal has_masnys), but the same applies.