Rails Pass A Parameter To Conditional Validation - ruby-on-rails-3

I'm importing heaps of student data from an spreadsheet document. Each row of student data will represent a new user, however, the possibility of importing an already existing student exists and I want to bypass some of my user validations such as username uniqueness accordingly so that I can build associations for both new and existing records, but only if they're being imported to the same school.
Thus far I have the following validation setup in my User model:
user.rb
validates_uniqueness_of :username, :unless => :not_unique_to_school?
def not_unique_to_school?
user = find_by_username(self.username)
user.present? && user.school_id == 6
end
Now how would I go about replacing that 6 with a value I have access to in the controller? Instructors will be the ones handling the importing and they'll be importing students to their school so I would typically run current_user.school_id to retrieve the school id that I want them to be imported to, but I don't have access to the current_user helper in my model.
I'm not concerned about duplicating usernames as I'll be handling that on a different step, this is just the preliminary validation.
Edit
Simplified school & user model:
user.rb
class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :username,
:first_name, :last_name, :school_id, :roles_mask
belongs_to :school
validates_presence_of :username, :on => :create, :message => "can't be blank"
validates_uniqueness_of :username, :unless => :unique_to_school?
def unique_to_school?
user = find_by_username(self.username)
user.present? && user.school_id == 6
end
def find_by_username(username)
User.where(:username => username).first
end
end
school.rb
class School < ActiveRecord::Base
attr_accessible :country_id, :name, :state_id
has_many :users
end

I'd add a method to your School model:
def student_named?(name)
self.users.where(:username => name).any?
end
then in your validation:
def not_unique_to_school?
self.school.student_named?(self.username)
end

Here's what ended up working for me:
validate :user_cant_be_duplicate_in_other_schools
def user_cant_be_duplicate_in_other_schools
errors.add(:username, :taken) if User.count(:conditions => ["school_id != ? AND username = ?", self.school_id, self.username]) > 0
end
As opposed to testing if a User belongs to a particular school we're testing for the lack of belonging to a particular school. I didn't come up with this answer, another user posted this as an answer but deleted it shortly after for reasons unknown.

Related

Rails 3.2 - Validate in Model Based on Another Model

I have an AWARD model - there are two forms to create an AWARD. One is for nominating EMPLOYEES, the other is for Non-Employees. The EMPLOYEE form pulls a list of active employees to populate the Nominee selection box. The Non-Employee form has only text fields to populate the Nominee field (because I have no source to populate a selection list).
To dummy-proof the app, I want to run a validation that disallows Employees from nominating themselves (because they will inevitably try to do so!). There is a hidden field on each form to set whether the form is Employee or Non: <%= f.hidden_field :employee, :value => true/false %>
So, on the Non-Employee form, if the user types in his own nominee_username, it should throw an error that says he cannot nominate himself.
I have a validation, but the error throws even if the nominee_username DOES NOT match the nominator. So, there is a problem with my validation.
Here's what I've attempted:
class Award < ActiveRecord::Base
belongs_to :nominator, :class_name => 'Employee', :foreign_key => 'nominator_id'
belongs_to :nominee, :class_name => 'Employee', :foreign_key => 'nominee_id'
validate :cant_nominate_self_non_employee_form,
:on => :create, :unless => :employee_nomination?
def employee_nomination?
self.employee == true
end
##### the validation below is not working properly - it throws error every time ####
##### only if Employee.username is equal to award.nominee_username, it should error ####
def cant_nominate_self_non_employee_form
if Employee.where(:username => nominee_username)
errors.add(:nominator, "can't nominate yourself")
end
end
end
There is an association between the Award and Employee models:
class Employee < ActiveRecord::Base
has_many :awards, :foreign_key => 'nominator_id'
has_many :awards, :foreign_key => 'nominee_id'
end

Trouble with Mass-Assignment Error for Admin User

I was trying to follow the railscasts tutorial that explains how to handle mass-assignment errors and attr_accessible for admins, but since that was a little outdated, I'm trying to follow what's in the rails API dock for 3.2.6 here.
All I want to do is allow the admin user the ability to access the "winning" attribute for the Proposal Model on the Update action.
Here's my Proposal Model showing the current attr_accessible.
class Proposal < ActiveRecord::Base
attr_accessible :email, :email_confirmation, :link, :name, :references, :short_description
belongs_to :idea
Here's my code for the Proposal Controller's Update action.
class ProposalsController < ApplicationController
include ActiveModel::MassAssignmentSecurity
attr_accessible :email, :email_confirmation, :link, :name, :references, :short_description
attr_accessible :email, :email_confirmation, :link, :name, :references, :short_description, :winning, :as => :admin
def update
#idea = Idea.find(params[:idea_id])
#proposal = #idea.proposals.find(params[:id])
if #proposal.update_attributes(proposal_params)
redirect_to idea_proposals_url(#idea)
else
render 'edit'
end
end
protected
def proposal_params
role = current_user.admin ? :admin : :default
sanitize_for_mass_assignment(params[:proposal], role)
end
Check out this Railscast. I had a similar issue with an Admin field Boolean and didn't want any user to circumvent the security by sending a curl post. If the user is an admin then it gave them the ability to access the field, otherwise Mass Assignment would protect the field from being modified.
http://railscasts.com/episodes/237-dynamic-attr-accessible?view=asciicast

Rails: Need help with nested form rejecting nested object but still creating main object

I have the following in my user model
class User < ActiveRecord::Base
has_many :gym_users
attr_accessible :gym_users_attributes, :gym_users
accepts_nested_attributes_for :gym_users, :reject_if => lambda { |a| a[:role_id].blank? }
end
This correctly rejects the gym_user model if the role_id is not present, the problem is it still creates the user and simply doesn'
t create the gym_user. Is there a way to make it not create or delete the user when the gym_user is rejected?
You can add
validates_associated :gym_users
to your User model and move validation from reject_if to GymUsers model
validates_presence_of :role_id
Add validates :gym_users, :presence => true to your User model

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