in my Rails app I have a User model and a Team model, linked through a User_Team model.
What I'm trying to do is to ask Rails to validate and create a new user only if the params used to create it come with a valid team_code param.
Basically I need to:
check the Team table
look if a team with the provided team_code exists
only in this case allow the creation of the user
link the user to the team
Which is the best way to do this? Where should I put the logic? In the controller? In the user validation model?
Even just a few hints would be helpful!
UPDATE #1
The team_code is an attribute of the Team model. Teams are created previously (not during user creation). I need that each user - in order to be created - has an existing team to enter at user creation time.
Thanks,
Augusto
Validation logic belongs in the model. Here's how I'd do it:
class User
belongs_to :team
attr_accessor :team_code
def team_code
#team_code ? #team_code : (team ? team.team_code : nil)
end
def team_code= (value)
#team_code = value
self.team = Team.where('team_code = ?', value).first
end
validates_presence_of :team
validates_each :team_code do |record, attr, value|
record.errors.add attr, 'does not exist' if !value.blank? && !team
end
end
Related
So I have a project with authentication of users, and history tracking of some models. For the history I use the paper_trail gem, I have a problem of filtering the history to show to the user, I have configured it to track the current_user id into the Whodunnit field.
My user has role_id which specifies the role from the Roles table
Also i have another table with some items that have id and user_id fields. And now my problem is how to take specific rows from Versions table according to the role of the user, like for ex: if user role is 'SomeRole' it has to return only those actions done by the users that have the same role 'SomeRole'.
I know that i can take out all the actions by
#versions = PaperTrail::Version.order('created_at')
but have no idea on how to filter to select only those that are satisfying for my uses. Is there an easy way to do it or should i hardcode it like selecting one by one, than check all user_id of their roles and so on so forth? Hope you understood my messy way of explaining
In similar situation I used this solution:
1) Added user_id field to versions
class AddUserIdToVersions < ActiveRecord::Migration
def change
add_column :versions, :user_id, :integer
add_index :versions, :user_id
end
end
(whodunnit is a text field and I didn't want to use it as association key)
2) Defined association in Version model:
# /app/models/version.rb
class Version < PaperTrail::Version
belongs_to :user
# ... some other custom methods
end
and in User model:
has_many :versions
3) Set info_for_paper_trail in ApplicationController (or where you need it)
def info_for_paper_trail
{ user_id: current_user.try(:id) } if user_signed_in?
end
So I'm able to access versions like:
#user.versions
So in your case it would be:
Version.joins(:user).where(users: { role: 'SomeRole' })
I am new to Ruby on Rails and I have created a project that contains a User table (generated by devise) and a AccountSetting table that contains user specific account settings (this table has a foreign key that relates to the id in the User table thus each User has zero or one AccountSettings). I have my seed data working fine, and I can seed the database with users that have user specific account settings. The User table is related to the AccountSetting table with a "has_one :accountsetting" and the AccountSettings table "belongs_to :user". This all works and makes sense. However, I have a method called "show_user_setting" in my UserSettings controller, and I do not know how to ONLY SHOW the account settings for that specific authenticated user.
So, how can I only display the user setting for the currently logged in user? Again, I am using devise.
My general idea of how to do this would be something like this. However I know this is incorrect, but for the purpose of an explanation, here it is.
def show_user_setting
#setting = AccountSetting.find(current_user)
end
My idea is that the #setting will contain the setting for the currently logged in user. Thanks in advance!
You should do this:
#app/models/account_setting.rb
class AccountSetting < ActiveRecord::Base
belongs_to :user
end
#app/models/user.rb
class User < ActiveRecord::Base
has_one :account_setting
end
This will allow you to call the following:
#setting = current_user.account_setting
Our Setup
For what it's worth, we do something similar:
#app/models/user.rb
class User < ActiveRecord::Base
before_create :build_profile #-> builds a blank profile on user create
has_one :profile
end
#app/models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
end
This allows us to put all sorts of different options inside the profile model (we have homepage etc):
The important thing to note here is that the above allows you to delegate various methods to the profile model, allowing you to call the following:
current_user.profile_name
current_user.profile_signin_redirect?
current_user.profile_avatar
etc
Have you tried
def show_user_setting
#setting = AccountSetting.find_by(user_id: current_user.id)
end
The way .find() works is it searches the model for the id passed. So, the way you currently have it is your going to try to search for the id of the model, when you want to find the foreign key. So use Model.find_by(column_name: param). You'll what to change user_id: to the column name of what you're storing the foreign key in, I'm just assuming it's something similar to that.
I'm guessing the show_user_setting function is part of a controller, if it is on a model then read this: accessing devise current_user within model
to set the #setting variable you should be able to do this
#setting = AccountSetting.find(user_id: current_user.id)
or
#setting = AccountSetting.find(user: current_user)
I'm still learning Rails, and using Devise. Currently I am working on a bug/ticket logging system. I'n this system we have tickets created by a user, assigned to another user and all users that can view it can post a reply on it.
I want to trigger an email when a user changes the status of a ticket to closed.
HOWEVER, if you are the creator (of the ticket) and you closed it, you do not want an email, but you want to email the user its assigned to. Likewise, if you are the assignee and you close it, you do not want to email, but you do want to email the creator. If you are neither creator or assignee, you still do not want an email, but you do want to email the other two.
The email will be a small notification noting ticket #_ is closed.
I am a bit tripped up as to where this code should go. There is no new code in the controller but I added a before_update :email_update in my ticket model.
def email_update
#status field is changed
if status_changed? && status.description == "Closed"
if(current_user != assigned_to)
UserMailer.new_ticket_admin(assigned_to, self).deliver
end
if(current_user != user)
UserMailer.new_ticket_admin(user, self).deliver
end
end
end
But, is this not bad practice to access the current user in one of the models? What would be a better approach?
Pretty sure, but I don't think that you can access current_user in the model. Even if you could, might I suggest an alternative. Instead, I would use a closed_by_id attribute where it is the current_user's ID. This way you can also track who closed a ticket. From here, you can check to see if the ticket is closed and if the creator of the ticket's ID is equal to the closed_by_id.
As you mentioned you have a creator and a 'closer' (or whatever you want to call that user). Within your user model you want to have something like this:
class Ticket < ActiveRecord::Base
belongs_to :requested_by, class_name: 'User' # foreign_key requested_by_id
belongs_to :closed_by, class_name: 'User' # foreign_key closed_by_id
def close(user)
self.closed_by = user
self.save
end
# bonus method
def closed?
closed_by?
end
end
def User < ActiveRecord::Base
has_many :tickets, foreign_key: 'requested_by_id'
has_many :closed_tickets, foreign_key: 'closed_by_id'
end
And for your controller something like:
class TicketController < ApplicationController
def create
#ticket = current_user.tickets.build params[:ticket]
end
def close
#ticket = Ticket.find(params[:id])
#ticket.close current_user
end
end
This way there is no need to have current_user within your model. Which probably solves your challege.
Say I have three models with users, roles, and events. Each user can have many roles (e.g. guitarist, bassist, etc) and each role can have one or more users (e.g. Ted can act as guitarist or a bassist) who can fulfil it.
I'm creating a rota application for a church band, so it means that every event has ONE OF EACH role in the band and one user to fill each role per event. I was wondering there was any way of enforcing this relationship in the model or whether I had to do this somewhere else in the application. Thanks for any help in advance!
Event should have many users through EventParticipation and EventParticipation should
belongs_to :user
belongs_to :event
Put some uniqueness validation for user_id, add a role_id and do a scoped uniqueness validation if you want.
I would do this with custom validation methods in the model. Here's some untested code but something like
class ChurchEvent < ActiveRecord::Base
validate :has_one_role_each
def has_one_role_each
errors.add(:base, "only one of each role allowed") if duplicate_roles?
end
private
def duplicate_roles?
# assuming band_roles is array of roles for this event
band_roles.uniq.count != band_roles.count
end
end
And something similar for the users.
I have a Topic model:
class Topic < ActiveRecord::Base
belongs_to :user
def validate_on_update
errors.add(:user, "Only the topic creator can update the topc") if
self.user != user;
end
end
I would like to check before every update that the existing topic.user is the same with the user that is trying to update the model.
I think that
self.user != user
is not working but I do not know how to fix that!
You need to find the record in the controller before doing that, so you can do this in your controller action:
#topic = current_user.topics.find(params[:id])
This will trigger an exception that you can easily catch or leave it.
This is the best method to ensure data integrity, unless you're tinkering in other places of the app and you need to create Topics not in controllers.
If you have such need, it's not bad to have a validation rule in the model to ensure major data integrity, but the model does need to know the user, that's only accessible from the controller.
My recommendation is that you assign the user controller-side or just use scopes like:
current_user.topics.create(params[:topic])
This way you are sure that the user is the same in question, and this invalidates the need to do another validation if it's the only place you're calling topic creation.
If you are unsure and wants to game on with a validate_on_update I suggest creating a virtual attribute like so:
attr_accessor :this_user
But in any case you'd pass this via controller, since your model should know nothing about the current logged in user:
#topic = Topic.new(params[:topic])
#topic.this_user = current_user # or #topic.user_id and check for a attr_changed?
Update: adding example as requested
# #topic = Topic.new(params[:topic])
# #topic.this_user = current_user
class Topic < ActiveRecord::Base
belongs_to :user
attr_accessor :this_user
def validate_on_update
# Make sure that this_user is an instance of User, otherwise just use the id
errors.add(:user, "Only the topic creator can update the topic") if user_id != this_user.id;
end
end
Update:
another suggestion is to use:
attr_readonly :user_id