Validate multiple belongs_to relations - ruby-on-rails-3

Let's say we have the following data structure (imagine as a loop).
MyModel * -- 1 User 1 -- * Clients 0 -- * MyModel
Thus, the MyModel looks like:
class MyModel < ActiveRecord::Base
belongs_to :client
belongs_to :user
attr_accessible :user_id, :client_id, # ...
validates :user_id, :presence => true
So it can belong to a client but must belong to a user. Also a client must belong to a user.
But how can I assert, that if someone saves a MyModel instance belonging to a client, that the client actually belongs to the user? Do I have to write a custom validator for this or is there any validation shortcut I overlooked?
Solution:
Following p.matsinopoulos' answer, I now did the following. I don't assign the foreign keys to MyModel before saving but rather the objects directly.
# my_model_controller.rb
def create
m = MyModel.create(whitelist_parameters(params[:my_model]))
m.user = current_user
if(params[:my_model][:client_id].present?)
client = Client.find params[:my_model][:client_id]
end
# ...
end
This way I can at first validate if there is a Client with such an ID at all. Then I implemented the custom validator by p.matsinopoulos. It's not tested yet, but since I am now working with the objects, it should do the trick.

If I were you, I would have written a custom validator.
validate :client_belongs_to_user
protected
def client_belongs_to_user
errors[:client] << 'must own the user' if client.present? && client.user != user
end

I would suggest, building on what you have:
class MyModel < ActiveRecord::Base
belongs_to :client
has_one :user, :through => :client
attribute_accessor :client_id
validates :client_id, :presence => true
...
end
class Client < ActiveRecord::Base
has_many :my_models
belongs_to :user
attribute_accessor :user_id
validates :user_id, :presence => true
...
end
class User < ActiveRecord::Base
has_many :clients
...
end

Related

Uniqueness validation with a has_many relationship

My model structure is as follows:
class Client < ActiveRecord::Base
has_many :charts
end
class Chart < ActiveRecord::Base
belongs_to :client
has_many :chart_data
end
class ChartDatum < ActiveRecord::Base
belongs_to :chart
end
ChartDatum has an attribute called 'name' which needs to be unique for each client.
I tried using "validates_uniqueness_of :name, :scope => [:chart_id]" but this helped me getting a unique key for a particular chart but not for all charts for a particular client. I am looking for something like "validates_uniqueness_of :name, :scope => [:client_id]" but obviously with the current structure it will not work out.
Could someone please help me?
Since you need unique name for chart_data for each client, you can try writing a custom validation for name something like this :
class ChartDatum < ActiveRecord::Base
belongs_to :chart
validates :name, :uniqueness => true, unless => :unique_for_client?
def unique_for_client?
client = self.chart.client
client.charts.chart_data.pluck(:name).include?(self.name)
end
end

Added two "belongs_to" to a Comment model but unable to get one of the associations

I am currently building very simple Comment system on Rails. The primary models are User, Albumpost, and Comment. Users can post Albumposts. For each Albumpost, Users can add Comments to the Albumpost. As a result, a Comment belongs to a User and belongs to an Albumpost.
The problem I'm having is that even with the proper associations in my models (see below), I can't get
#comment.user.name
when I'm trying to render the comments in the albumpost 'show' page (/views/albumposts/show.html.erb). When I go to the page, I can't get #comment.user.name (doesn't understand the association) and get a
"undefined method `name' for nil:NilClass"
Oddly I can get
#comment.albumpost.content
I've double-checked my models and also added the proper foreign keys to the models. Am I doing something wrong in the controllers?
Here are my models:
class Comment < ActiveRecord::Base
attr_accessible :body, :albumpost_id, :user_id
belongs_to :albumpost
belongs_to :user
end
class Albumpost < ActiveRecord::Base
attr_accessible :content
belongs_to :user
has_many :comments, dependent: :destroy
end
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_many :albumposts, dependent: :destroy
has_many :comments, dependent: :destroy
end
Here are the relevant parts of my Albumpost and Comments controllers:
class AlbumpostsController < ApplicationController
def show
#albumpost = Albumpost.find(params[:id])
#comments = #albumpost.comments
#comment = Comment.new
#comment.albumpost_id = #albumpost.id
#comment.user_id = current_user.id
end
end
class CommentsController < ApplicationController
def create
albumpost_id = params[:comment].delete(:albumpost_id)
#comment = Comment.new(params[:comment])
#comment.albumpost_id = albumpost_id
#comment.user_id = current_user.id
#comment.save
redirect_to albumpost_path(#comment.albumpost)
end
end
I think you should prefer setting objects to relations instead of setting their ids. For example, you should do this:
#comment.user = current_user
instead of
#comment.user_id = current_user.id
ActiveRecord will take care of setting corresponding *_id fields. I'm not sure how it handles the reverse. (it should autoload though, if I understand correctly)

What's the rails way to include a field in a join model when listing an association?

So if I have the following relationship
class Item < ActiveRecord::Base
has_many :item_user_relationships
has_many :users, :through => :item_user_relationships
end
class User < ActiveRecord::Base
has_many :item_user_relationships
has_many :items, :through => :item_user_relationships
end
class ItemUserRelationship < ActiveRecord::Base
belongs_to :item
belongs_to :user
attr_accessible :role
end
What's the rails way to include the role attribute when listing all the Users of an Item?
#users = #item.users # I want to include the role as part of a user
Thanks!
UPDATE: I'm still having trouble with this. My goal is to get an array of User models that have their role included in the attributes.
I'm note sure if I understand you correctly, but maybe this is what you want?
#users = #item.users
#users.each do |user|
puts user.item_user_relationships.first.role
end

CanCan Separate Role Model

I've been following this guide on the Separate Role Model implementation in CanCan. When a User, tries to sign up this error is thrown when creating the Assignment.
User(#21477600) expected, got Symbol(#5785720)
I'm using a Devise generated User with the following before_save functions
class User < ActiveRecord::Base
.
.
.
def create_profile
profile = Profile.new :user_id => :id
end
def create_role
Assignment.new :user => :id, :role => Role.find_by_role("user").id
end
end
I want to default the user's role to "user", but I'm obviously doing something wrong. How should this be implemented?
Not sure if you've seen this or not, but Ryan Bates has produced a wonderful document regarding:
Separate Role Models
EDIT:
Here's what I am currently using. I believe your 'Assignment' is the same as my 'UserRole'.
user.rb
#--
# Relationship
has_many :user_roles, :dependent => :destroy, :uniq => true
has_many :roles, :through => :user_roles, :uniq => true
#--
# Instance Method
# Determine if the user has a specified role
# You can find this method at: https://github.com/ryanb/cancan/wiki/Separate-Role-Model
# Written by Ryan Bates, I added the downcase though to detect 'Admin' vs 'admin'.
# Example:
# user.has_role? :Admin
# => true
def has_role?(role_sym)
roles.any? { |role| role.name.underscore.to_sym == role_sym.downcase }
end
role.rb
# id :integer(4) not null, primary key
# name :string(255)
#--
# Relationship
has_many :user_roles, :dependent => :destroy, :uniq => true
has_many :users, :through => :user_roles, :uniq => true
user_role.rb
# id :integer(4) not null, primary key
# user_id :integer(4)
# role_id :integer(4)
#--
# Relationship
belongs_to :user
belongs_to :role
Then in my ability.rb
def initialize(user)
user ||= User.new # in case of a guest
if user.has_role? :Admin # The user is an Administrator
can :manage, :all
else
can :read, :all
end
end
Then I can easily assign roles, like in my seed file by doing something like:
# Create Users
...
# Roles
admin = Role.create!(:name => "admin")
standard = Role.create!(:name => "standard")
# UserRoles :Admin
user1.roles << admin
user2.roles << standard
So by calling user.roles << [role_name], I am essentially creating a UserRole, which has a user_id and a role_id.
There might be some more effective ways to accomplish this, but I cant tell without the exact model associations.
Anyway, I think this should work:
def create_role
Assignment.new :user => self, :role => Role.find_by_role("user")
end
Since you specify :user and not :user_id, you should pass self. The same thing for :role. If you had specified :role_id then you should have entered .id after find_by_role but since you only specify :role then remove .id
It looks like you're passing symbols to hash conditions that are expecting objects.
DanneManne's answer should work. You could alternatively do
Assignment.new( :user_id=>self.id, :role_id => Role.find_by_role('user').id )
(but Danne's is better, imo)
One last suggestion -- why not say the name of the role is "name", not "role". So then you'd be doing, Role.find_by_name('user'). That would be easier for a subsequent programmer to follow.
Firstly, you should not use save callback because it will be fired on both create & update.
Secondly, if you set up associations between models like that:
class User < ActiveRecord::Base
has_one :profile
has_many :assignments
end
class Profile < ActiveRecord::Base
belongs_to :user
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
You will have convenient methods like user.profile, user.build_profile and user.create_profile. Build & create will set up user_id on profile automatically. You can use them in your callbacks without having to define any methods.
Note that before user is saved it does not have an id. So you need to use either before_create :build_profile either after_create :create_profile. The first one will create profile in memory that will be autosaved after user is saved, the second one is pretty straightforward.
There will be similar methods for assignments too: user.assignments.build user.assignments.create. So the final code for User will look something like this
class User < ActiveRecord::Base
has_one :profile
has_many :assignments
after_create :create_profile, :create_assignment
def create_assignment
assignments.create :role => Role.find_by_role("user")
end
end

How do i create an object if it has more than one belongs_to?

I have the following:
class Org < ActiveRecord::Base
has_many :users
has_many :entries
end
class Entry < ActiveRecord::Base
belongs_to :org
belongs_to :user
validates_presence_of :entry_text
end
class User < ActiveRecord::Base
belongs_to :org
has_many :entries
validates_uniqueness_of :user_name
validates_presence_of :user_name, :length => { :minimum => 3 }
end
I can Create Orgs and Users... How do i create an entry if there are two belongs_to? and what is this pattern called?
Double nested resources are tricky. The trick with users usually is to keep it out of your desired entry path.
Your question is kind of broad, but if you specify more information, people would be able to help you better. Also, I would recommend using the gem Devise for your user management system. Since you're using 'users' I would assume you want users from orgs to create entries. The entry created would be a part of org and the user would be the session's current user. Sorry if I am wrong to assume this.
Your routes.rb file can look something like this (assuming rails 3):
resources :orgs do
resources :entries
end
Then the create of your entry controller would look like:
#entry = #org.entries.new(params[:topic])
#entry.user = current_user #or however you are managing the current user's session.
And you'd want to set the org for the entire class by making a method that loads your current org and do a before_filter :loadOrg
def loadOrg
#org = Org.find(params[:id])
end
This is of course assuming your path is something like: /org/(id)/entry/(entry_id)
and not
/org/(id)/user/(user_id)/entry/(entry_id)
which in my opinion is unnecessary and can lead to more problems. You can always create a userpage model that calls all entries by users, but the default route doesn't necessarily have to include users in the path.
I don't see any problem.
#entry = Entry.create(:entry_text => "Hello World!")
Now questions to clarify what do you need:
Can #entry belongs both org and user at the same time? Or it can belongs to only one of them?
Should #entry belongs to at least one of them?
If #entry supposed to belong only one of them, so you should use Polymorphism
http://railscasts.com/episodes/154-polymorphic-association
class Entry < ActiveRecord::Base
belongs_to :textable, :polymorphic => true
validates_presence_of :entry_text
end
class Org < ActiveRecord::Base
has_many :users
has_many :entries, :as => :textable
end
class User < ActiveRecord::Base
belongs_to :org
has_many :entries, :as => :textable
validates_uniqueness_of :user_name
validates_presence_of :user_name, :length => { :minimum => 3 }
end