If I have a user that has_many user_logins and a user_logins that belongs to user - When a user_login is created I'm using UserLogin.create(userlogin_params) and then my strong params permits the user_id column - but this alone is not saving the current users I.D to the column as it is coming out as nil.
How do I make it save the I.D?
User model:
has_many :user_logins
UserLogin model
belongs_to :user
accepts_nested_attributes_for :user
UserLoginController:
...
def create
#user_login = UserLogin.new(user_login_params)
...
end
...
def user_login_params
param.require(:user_login).permit(
:user_login_attribute1,
:user_login_attribute2,
user_attributes: [
:id,
:user_attribute1,
:user_attribute2
]
)
end
Tell me if it helps.
There are 2 issues here at hand.
First: How do you create an association with the parent record automagically there?
Second: How do you do this so your controller action isn't a giant hole waiting for a hacker to stick their nose in.
You need to start from the parent, then build the child, not start with the child and build the parent.
Consider the following:
class User < ActiveRecord::Base
has_many :logins, class_name: "UserLogin"
end
class UserLogin < ActiveRecord::Base
belongs_to :user
end
class UserLoginsController < ApplicationController
def create
if new_user_login(user_login_params).save
redirect_to :wherever
else
render :new
end
end
private
def new_user_login(attrs={})
current_user.logins.create(attrs)
end
def user_login_params
param.require(:user_login).permit(:attr_1, :attr_1)
end
Do not pass IDs into any secure params hash unless that ID is selectable by the user. If you allow an ID into secure params, a hacker can start moving records around to other objects and destroy your database integrity.
If you would like pairing help on this problem live and in person, you can check out my codementor profile at https://codementor.io/rubycasts/#reviews
Related
I have a User model that gets created through Devise, and after it's creation, I would like to automatically create a new Client (another model in my app). The new Client's atrribute, :user_id, should be equal to the :id of the User that was just created. I believe I need to use something like:
class Users::RegistrationsController < Devise::RegistrationsController
after_create :create_client
def create_client
Client.create(:user_id, :id) # Not sure what should go here
end
end
Is this the correct way to accomplish this? Also, if associations are important Client belongs_to :user and User has_one :client
You can add an after_create callback in User model(user.rb), check here for more information on how to create has_one associations.
class User < ActiveRecord::Base
after_save :add_client
def add_client
self.create_client(client_attribute1: value, client_attribute2: value)
end
end
I am currently trying to build a simple learning app with rails and I am facing problems. When a user signs up to a course i try to make a viewed_course record for them so that I can display all the viewed_course on their profile page.
I can't figure out how to set my different controllers so that it creates the viewed_record when I am on the Course Controller /index.
How can I modify the course/index view so that it creates a new viewed record for the current signed in user.
I am using devise.
class Course
has_many :lessons
end
class Lesson
#fields: course_id
belongs_to :course
end
class User
has_many :viewed_lessons
has_many :viewed_courses
end
class ViewedLesson
#fields: user_id, lesson_id, completed(boolean)
belongs_to :user
belongs_to :lesson
end
class ViewedCourse
#fields: user_id, course_id, completed(boolean)
belongs_to :user
belongs_to :course
end
Thank you so much in advance for helping me out!
Something like this should do it, this is in courses controller show action (index action should be the list of all courses, show action should be viewing an individual course):
class CoursesController < ApplicationController
def index
#courses = Course.all
end
def show
#course = Course.find(params[:id])
current_user.viewed_courses.create(course: #course)
end
end
One gotcha is that this would create a new viewed course every time they viewed the page, you would need some logic to only add each course once.
Perhaps something like this:
def show
#course = Course.find(params[:id])
current_user.viewed_courses.create(course: #course) unless ViewedCourse.exists?(user_id: current_user.id, course_id: #course.id)
end
Let's keep this simple. Let's say I have a User model and a Post model:
class User < ActiveRecord::Base
# id:integer name:string deleted:boolean
has_many :posts
end
class Post < ActiveRecord::Base
# id:integer user_id:integer content:string deleted:boolean
belongs_to :user
end
Now, let's say an admin wants to "delete" (hide) a post. So basically he, through the system, sets a post's deleted attribute to 1. How should I now display this post in the view? Should I create a virtual attribute on the post like this:
class Post < ActiveRecord::Base
# id:integer user_id:integer content:string deleted:boolean
belongs_to :user
def administrated_content
if !self.deleted
self.content
else
"This post has been removed"
end
end
end
While that would work, I want to implement the above in a large number of models, and I can't help feeling that copy+pasting the above comparative into all of my models could be DRYer. A lot dryer.
I also think putting a deleted column in every single deletable model in my app feels a bit cumbersome too. I feel I should have a 'state' table. What are your thoughts on this:
class State
#id:integer #deleted:boolean #deleted_by:integer
belongs_to :user
belongs_to :post
end
and then querying self.state.deleted in the comparator? Would this require a polymorphic table? I've only attempted polymorphic once and I couldn't get it to work. (it was on a pretty complex self-referential model, mind). And this still doesn't address the problem of having a very, very similar class method in my models to check if an instance is deleted or not before displaying content.
In the deleted_by attribute, I'm thinking of placing the admin's id who deleted it. But what about when an admin undelete a post? Maybe I should just have an edited_by id.
How do I set up a dependent: :destroy type relationship between the user and his posts? Because now I want to do this: dependent: :set_deleted_to_0 and I'm not sure how to do this.
Also, we don't simply want to set the post's deleted attributes to 1, because we actually want to change the message our administrated_content gives out. We now want it to say, This post has been removed because of its user has been deleted. I'm sure I could jump in and do something hacky, but I want to do it properly from the start.
I also try to avoid gems when I can because I feel I'm missing out on learning.
I usually use a field named deleted_at for this case:
class Post < ActiveRecord::Base
scope :not_deleted, lambda { where(deleted_at: nil) }
scope :deleted, lambda { where("#{self.table_name}.deleted_at IS NOT NULL") }
def destroy
self.update(deleted_at: DateTime.current)
end
def delete
destroy
end
def deleted?
self.deleted_at.present?
end
# ...
Want to share this functionnality between multiple models?
=> Make an extension of it!
# lib/extensions/act_as_fake_deletable.rb
module ActAsFakeDeletable
# override the model actions
def destroy
self.update(deleted_at: DateTime.current)
end
def delete
self.destroy
end
def undestroy # to "restore" the file
self.update(deleted_at: nil)
end
def undelete
self.undestroy
end
# define new scopes
def self.included(base)
base.class_eval do
scope :destroyed, where("#{self.table_name}.deleted_at IS NOT NULL")
scope :not_destroyed, where(deleted_at: nil)
scope :deleted, lambda { destroyed }
scope :not_deleted, lambda { not_destroyed }
end
end
end
class ActiveRecord::Base
def self.act_as_fake_deletable(options = {})
alias_method :destroy!, :destroy
alias_method :delete!, :delete
include ActAsFakeDeletable
options = { field_to_hide: :content, message_to_show_instead: "This content has been deleted" }.merge!(options)
define_method options[:field_to_hide].to_sym do
return options[:message_to_show_instead] if self.deleted_at.present?
self.read_attribute options[:field_to_hide].to_sym
end
end
end
Usage:
class Post < ActiveRecord::Base
act_as_fake_deletable
Overwriting the defaults:
class Book < ActiveRecord::Base
act_as_fake_deletable field_to_hide: :title, message_to_show_instead: "This book has been deleted man, sorry!"
Boom! Done.
Warning: This module overwrite the ActiveRecord's destroy and delete methods, which means you won't be able to destroy your record using those methods anymore. Instead of overwriting you could create a new method, named soft_destroy for example. So in your app (or console), you would use soft_destroy when relevant and use the destroy/delete methods when you really want to "hard destroy" the record.
In Rails 3.2 I have been looking for a way to traverse the associations of an object within the before_add callback.
So basically my use case is:
class User < ActiveRecord::Base
has_and_belongs_to_many :meetings
end
class Meeting < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :comments, :before_add => :set_owner_id
end
class Comment < ActiveRecord::Base
belongs_to :meeting
end
def set_owner_id(child)
child.owner_id = <<<THE USER ID for #user >>>
end
and I am creating a comment within the context of a user:
#user.meetings.first.comments.create
How do I traverse the associations from within the before_add callback to discover the id of #user? I want to set this at model level. I have been looking at proxy_association, but I may be missing something. Any ideas?
You should probably create the comment in the context of the meeting, no? Either way, you should handle this in the controller since you'll have no access to #user in your model.
#comment = Meeting.find(id).comments.create(owner_id: #user, ... )
But if you insist on your way, do this:
#comment = #user.meetings.first.comments.create(owner_id: #user.id)
Say you have this structure:
class House < ActiveRecord::Base
has_many :rooms
accepts_nested_attributes_for :rooms
attr_accessible :rooms_attributes
end
class Room < ActiveRecord::Base
has_one :tv
accepts_nested_attributes_for :tv
attr_accessible :tv_attributes
end
class Tv
belongs_to :user
attr_accessible :manufacturer
validates_presence_of :user
end
Notice that Tv's user is not accessible on purpose. So you have a tripple-nested form that allows you to enter house, rooms, and tvs on one page.
Here's the controller's create method:
def create
#house = House.new(params[:house])
if #house.save
# ... standard stuff
else
# ... standard stuff
end
end
Question: How in the world would you populate user_id for each tv (it should come from current_user.id)? What's the good practice?
Here's the catch22 I see in this.
Populate user_ids directly into params hash (they're pretty deeply nested)
Save will fail because user_ids are not mass-assignable
Populate user for every tv after #save is finished
Save will fail because user_id must be present
Even if we bypass the above, tvs will be without ids for a moment of time - sucks
Any decent way to do this?
Anything wrong with this?
def create
#house = House.new(params[:house])
#house.rooms.map {|room| room.tv }.each {|tv| tv.user = current_user }
if #house.save
# ... standard stuff
else
# ... standard stuff
end
end
I haven't tried this out, but it seems like the objects should be built and accessible at this point, even if not saved.