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
Related
With Rails 5, what's the most elegant way to write a replace save method based on a uniqueness constraint in my model? This is my model with the two columns that define a unique constraint
class Vote < ApplicationRecord
belongs_to :person
belongs_to :user
validates_uniqueness_of :person, scope: [:user]
end
In my controller, I'd like to invoke logic to save a vote, by doing
# Rate someone!
def create
user = current_user
#vote = Vote.new(vote_params)
#vote.user = user
#vote.save!
end
However the above doesn't work if the user has already voted on that person. In such a case, i'd like to delete their previous vote and create a new vote (or edit the existing one). Is there an easy way to do a transactional insert/update based on whether the unique constraint exists?
You should use find_or_create_by.
# Rate someone!
def create
#vote = Vote.find_or_create_by(person_id: vote_params[:person_id])
#vote.user = current_user
#vote.save!
end
I would go with first_or_create
def create
#vote = Vote.where(person_id: vote_params[:person_id], user: current_user).first_or_create.update(vote_params)
end
It will look for the Vote with the current User and Person, if it exists it will Update it, if it doesn't it will create it.
(It will make only one INSERT)
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.
I'm developing an application in which a Course belongs to a user. I would like to predefine a number of courses, such that a Course's template details are then copied into the users course details. From this initial point each user has a one-to-one mapping with a course.
I'd like to know the best place for the static attributes for building a user's course.
Thanks,
Adam
You could use a before_create or after_create filter on your user model, something like this:
before_create :add_default_courses
def add_default_courses
self.courses << Course.new({:foo => 'bar'});
end
you can initialize courses after a user is created.
User.rb #you user file
def after_initialize
self.courses << add_courses
end
private
def add_courses
#add_courses = Courses.find(:all, conditions => [])
end
I'm currently developing an application whereby a user clicks a button and they're offered up a new page of content, and was wondering how I would go about hiding or skipping past those that the user has already interacted with (a separate table stores the post_id and user_id for each view).
I currently use this code in the model for displaying a random page:
def self.random
if (c = count) != 0
find(:first, :offset =>rand(c))
end
end
The user authentication system is built off of Authlogic, and I have User, Post and View models.
So if a user has already seen a post "foo", how would I not display that in the future and instead serve up a random "bar".
Thanks
Steve,
I would set a boolean field for each post called "read" (default => false).
Upon firing the "show" action of your controller (and any other action you consider the person seeing the post), you can automatically set that to true and perform a save without validation. When you then show your list of records, you can add the condition .where("read = ?", false).
Of course, you can decide whether you want to give users the flexibility of setting individual posts to 'unseen' or 'unread' - if you want to do that it's the subject of another question :).
You could store an array of viewed post ids on the session in the show action of the posts_controller. EDIT -- find random post not already viewed. Not tested, but the idea is here:
def show_random_post
while (id == nil || (session[:viewed_posts] ||= []).include?(id)) # initialize array if it hasn't been initialized
id = rand(Post.count) + 1
end
session[:viewed_posts] << id
#post = Post.find(id)
# etc.
end
Do you want to keep a record of viewed posts between sessions?
EDIT: If you want to keep a user-level record of viewed posts between sessions, you'll probably want to do it at the db level. Since this means a many-to-many relationship between users and posts, you'll likely want to manage that with a relational table, and the best way to do that in Rails is with has_many :through. Something like (again, not tested):
class ViewedPostRecord < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
class User < ActiveRecord::Base
has_many :viewed_post_records
has_many :viewed_posts, :class => 'Post', :through => :viewed_post_records
end
class PostsController < ApplicationController
def show_random_post
while (id == nil || current_user.viewed_posts.map(&:id).include?(id))
id = rand(Post.count) + 1
end
#post = Post.find(id)
current_user.viewed_posts << #post
# etc.
end
end