I have model, and when i write into it, i'm using standart Model.create!(params[:somesymbol])
However in my form i have hidden field with current_user.id
current_user its variable from devise, which returns user that is logged. So when i create new record, i believe to user, that he didnt change value in hidden field. Is there any ways to avoid this, and write into model in some way like this
Model.create!(params[:somesymbol], user_id: current_user.id) ?
You can use :
Model.create!(params[:somesymbol].merge(user_id: current_user.id))
The user_id in params will be overridden by current_user.id (see doc)
Related
I'm building a training website where I have two models, User and Course, that are associated with a third model, CourseCompletions. The third model is for keeping track of which user has completed which courses and vice versa. The first two models have controllers whereas the third one does not.
I implemented the functionality for completing a course and it works (clicking the "complete course" button on the course page inserts the appropriate row into the course_completion table if the user has not completed that course before), but I'm unsure about how robust and secure my implementation is. This is in Course_Controller.rb:
helper methods omitted for brevity
def complete_course
#course = current_course
#user = current_user
if !already_completed
#course.course_completions.create(user_id: #user.id, course_id: #course.id, completion_date: Time.now)
flash[:success] = "Congratulations! Your progress has been saved."
redirect_to course_path
else
flash[:success] = "Looks like you have already completed this course before, but mad props for reviewing it!"
redirect_to course_path
end
end
My questions are as follows:
Should I be calling create like I am doing, or is build (or create!) a better option?
Should I be using strong parameters inside that function? If so, how do I do that in this particular case?
Thank you in advance.
Change this line :
#course.course_completions.create(user_id: #user.id, course_id: #course.id, completion_date: Time.now)
to
Course_completion.create(user_id: #user.id, course_id: #course.id, completion_date: Time.now)
or to
Course_completion.create(user: #user, course: #course, completion_date: Time.now)
or to
#course.course_completions.build(user: #user, completion_date: Time.now) # you can also use new instead of build, but build is preferred in this case
build is just an alias for new, but still the preferred way if you create an object through a collection like : #course.course_completions.build..
create is equivalent to .new + .save, it allow you save an object at one time.
create! is equivalent to .new + .save!, it's the same as create the only difference is that it throws an exception if the object won't save.
you don't need in this case to use strong parameters, strong parameters are important with forms to prevent the user updating non permitted fields, imagine you have a form with these fields : name, email, password, in this case a malicious user can add a field dynamically to your form using firebug for example let say admin, then he set the field to true, this is a security problem if you have a field called admin in your database, so to prevent the user to set this field (which normally is not in your form) we use strong parameters to specify only the fields we allow the user to update.
update :
to answer your comment about the difference between the 3 code parts above :
there is no difference between them, but if you want the two first are the same, either you write user_id: #user.id or user: #user, Rails is smart to understand that you want to set the foreign key which is user_id.
the third is just a different syntax or variation, instead of create a new object from the model Course_completion then insert the user_id and course_id like we did in the two first example, you have just to create a new object based on a collection, i mean by collection your "course_completions", so because your #course has_many course_completions (you can say that #course has a collection called course_completions)
to add a new object to your course_completions "collection" you have just to write #course.course_completions.build, then you pass the user_id and completion_date values to it, but what about course_id value ? the answer is that your collection is already based on #course (#course.course_completions) so you don't need to set the course_id, Rails know about it.
Hope this help
The code in question is in the create action of the Article controller.
I want each new article created to belong to the currently signed in User.
I checked that there is a user_id column in the Articles table and the correct association statements are in both models.
When I try and submit the form for a new Article, I get this error:
undefined method 'article' for nil:NilClass
in reference to this line of code:
#article = #current_user.article.new(article_params)
Am I using current_user incorrectly? How would one go about making the new item belong to the currently signed in user?
You should use current_user method:
#article = current_user.articles.new(article_params)
#current_user instance variable isn't set (thus it evaluates to nil) until current_user is called.
current_user is basically a convenience method/variable which takes the user id from the session hash and grabs that user (which is the current user that is active on that specific request)
so, when you want to have something only on the current user, just called current_user
however, there are some things you should watch out
make sure that the current user is logged in.
so make sure that when calling current_user.article.new(params) that current_user isn't nil, or it will throw an exception
when you want something to be only on that specific logged in user make sure to do as you did in the question
such as:
current_user.articles instead of Article.where(user_id: ...)
this will help you avoid problems in the future, and it may be faster in some situations
and 3. I think it should be current_user.articles.new(...)
It should be current_user, not #current_user.
current_user is a method, not an instance-variable.
I am creating a instance variable that gets passed to my view. This variable 'post' has a user_id associated with it and I wanted to add an extra attribute called 'username' so I can also pass that and use it in the view.
Here is an example of what I would like to do.
#post = Post.find(params[:id])
#post.username = User.find(#post.user_id).username
A username column does exist on my Users model but not my Songs model. So it won't let me use
#post.username
I know I can just make an entirely new instance variable and put that information in there but I would like to keep everything nice and neat, in one variable. Which will also make my json rendered code look cleaner.
Any ideas on how I can accomplish this?
Thanks!
Based on the presence of a user_id in your Post model, you probably already have an association set up that can retrieve the username. It will probably save a lot of trouble to simply use the existing association:
#post = Post.find(params[:id])
username = #post.user.username
If you're likely to be querying more than one post at a time (e.g., on an index page, calling .includes to tell Rails to eager-load an association will help you avoid the N+1 problem:
#posts = Post.includes(:user).all
Finally, to include the associated record in your JSON output, pass the :include parameter as you serialize:
# in controller
render :json => #post.to_json(:include => :user)
This question includes a much more comprehensive discussion of serialization options. Well worth a read.
No need to pass a separate instance variable.
1. You can use #post.user.username in view itself.
2. Or you can create a helper and pass #post.user
def username user
user.username
end
I have a form that allows a user to update their profile information, but I would like to prevent some information from being changed. I also would like to keep my controller code very simple. In the update action of my Users Controller, I have the following code:
def update
#user = Users.find params[:id]
if #user.update_attributes(params[:user])
flash[:notice] = 'Update successful.'
redirect_to user_path(#user)
else
render :action => :edit
end
end
This is very clean and simple, and I like that. What I don't like, however, is that a user can add a field to the form, with the same name as an attribute, and use it to modify forbidden attributes. Is there a simple way to do this, or do I need to devise a way to do this myself?
One method I was considering was to generate a hash value, using a hash-based message authentication code, of all the form's element names. This message access code would be a hidden value in the form. Then, once the form is submitted, I would calculate the message access code (MAC) again using the names of the parameter Hash's keys. If the two MACs are different, or if the first MAC is missing from the parameter Hash, I would throw an error. I would rather not spend the time implementing this if there was already and easy solution out there.
Thanks.
On your model you can use attr_protected or attr_accessible to blacklist or whitelist attributes when being set via mass assignment (like when a form is submitted).
Rails will prevent mass assignment if you use attr_protected :protectedcolumn (blacklist) or attr_accessible :safecolumn (whitelist) within your model. More information on this topic can be found in the Ruby on Rails Security Guide (Section 6.1)
I am using Ruby on Rails 3 and I would like to know some behaviours of this code:
#user.send :attributes=, #attrib, false
That is from here.
If I have a form that return these parameters to my controller:
params[:name]
params[:surname]
params[:email]
and in the controller I use
#user.send( # Avoiding 'attr_accessible'
:attributes=, {
:name => params[:name],
:surname => params[:surname] },
false )
#user.save
it should save only 'name' and 'surname' attributes for the #user ActiveRecord. I tryed that, and it works as expected. But...
1. is it possible that a malicious user can set the email value in someway on the saving process (also if the email attribute is not considered in the "send" statement)?
2. is it right the following statement?
Calling attributes= with false doesn't
update anything yet, it just sets the
attribute values while ignoring any
attr_accessible whitelist.
So you can just call save afterwards,
which returns the boolean value you're
looking for.
Your code will definitely not allow a malicious user to set the email. I would recommend using the following code though, since it performs the same thing and is easier to read:
#user.name = params[:name]
#user.surname = params[:surname]
#user.save
Also, passing true as the second parameter to attributes= allows you to protect your attributes using attr_protected and attr_accessible. You can view the documentation here. That means your statement is correct: passing false as the second attribute ignores your mass-assignment protected attributes.