i have some troubles with understanding of "params" function in RoR.
let's assume that i have user Model.
the next code apperars in users controller and it's function is to process POST 'destroy' request. "current_user" function returns currently signed in user (an instance of User class I suggest). by comparing "current_user == params[:id]" 'destroy' function checks if user is trying to delete yourself
def destroy
if current_user == params[:id]
flash[:error] = "you cannot delete yourself!"
else
User.find(params[:id]).destroy
flash[:success] = "user deleted"
end
redirect_to(users_path)
end
So the problem is that chunk code works well. and I don't really understand why. My background is 3-years experience of C++/C# programming in university, so I presumed that such kind of comparsion should cause some type casts. In that case I think it would be User obj ---> string obj (OR string --> User???!!!!).
Although I have a lot of questions about how Rails manages to compare User class and string class, I could make myself comfortable with this.
But what if I want to optimize this task and explicitly compare just IDs: the one stored in params[:id] as a string(??) and the other in current_user["id"] hash.
first is of string type and second is of integer, am I wrong? because "current_user["id"] == params[:id].to_i" causes error, that implies that params[:id] returns instance of User class o_O
thanks!
first of all: get yourself a decent ruby book and read about it's dynamic type system and method execution. that should answer most of your questions that you have when you come from a language like c.
like the opperator overloading in c it's possible in ruby to implement custom behavior for things like ==. this is very easy in ruby, because == is just a method. that's how you could write comparisons for multiple types of classes, even though those are not symmetric anymore.
in your case, the code that you provided is just wrong. comparing current_user with params['id'] will always yield false.
you should write something like that:
user = User.find params[:id]
if current_user == user
redirect_to users_path, :error => "you cannot delete yourself!"
else
user.destroy
redirect_to users_path, :notice => "user deleted"
end
Your current_user variable should contain the id in integer or string format. Or maybe your user model has a to_s method defined for user instances. In this case when trying to convert the object to a string (for the comparison with a string), this method will be called which will be returning the id in string format.
You should print both the current_user variable as well as the params[:id] to be sure.
you should do this
if current_user.id.to_s == params[:id]
params[:id] is a string, and you should comparing it with the current_user's id , not current_user
Related
In my controllers I often have functionality like this:
#account = Account.new(account_params)
if #account.save
if #account.guest?
...
else
AccountMailer.activation(#account).deliver_later
#account.update_column(:activation_sent_at, Time.zone.now)
flash[:success] = "We've sent you an email."
redirect_to root_path
end
end
What's the best way to send an email and update the activation_sent_at attribute without having to save the record twice? Calling update_column doesn't feel right to me here because AFAIK it creates an extra SQL query (correct me if I'm wrong).
Your code is fine. I wouldn't change it.
For example, you might be tempted to do something like this:
#account = Account.new(account_params)
#account.activation_sent_at = Time.zone.now unless #account.guest?
if #account.save
if #account.guest?
...
else
AccountMailer.activation(#account).deliver_later
flash[:success] = "We've sent you an email."
redirect_to root_path
end
end
Aside from the small issue that there's now repeated logic around #account.guest?, what happens if AccountMailer.activation(#account).deliver_later fails? (When I say "fails", I mean - for example - AccountMailer has been renamed, so the controller returns a 500 error.)
In that case, you'd end up with a bunch of account records which have an activation_sent_at but were never sent an email; and you'd have no easy way to distinguish them.
Therefore, this code warrants running two database calls anyway: One to create the record, and then another to confirm that an email was sent. If you refactor the code to only perform a single database call, then you'll become vulnerable to either:
Sending an email to a non-created user, or
Marking a user with activation_sent_at despite o email being sent.
The controller should be doing two transactions, not one. Which is why I said: Don't change it.
In every example for create or update action that I see, they have something like this.
def create
#user = User.new(params)
if #user.save
redirect_to #user
else
render 'new'
end
end
Here how the redirect_to #user goes to show action of the controller. Can anybody explain me this?
Let's start from the redirect_to documentation.
redirect_to post_url(#post)
is used to redirect to a specific URL generated using one of the Rails route helpers. In your case, it means you can write
redirect_to user_url(#user)
However, redirect_to also accepts a single model instance. Behind the scenes, redirect_to relies on url_for to generate an URL from the input when the input is not an object.
url_for, in turns, when you pass an instance of a model by default will compute the corresponding GET action to view the model.
In conclusion, the following code:
redirect_to #post
is equivalent to
redirect_to post_url(#post)
However, personally I prefer the explicit version. Even if it's a little bit longer, I've noticed it tends to produce more maintainable code in the long run. Writing the full route will allow you to easily search your code base when you need to debug or rename routes.
It's all in the documentation.
Record - The URL will be generated by calling url_for with the options, which will reference a named URL for that record.
So, url_for will be called on your #user which will produce the url for redirection. (/users/1234 or something)
This is just one of many ways to do redirection, by the way.
In Ruby (the language which supports Rails), you set #instance_variables to store data for that request. Whilst you can store many types of data in a variable, Rails often assigns #model objects to them...
#user = User.find 1
#-> #user = <User id: "1", name: "john" .... >
This means that whenever you use a helper (such as redirect_to, or even a path_helper), you're actually able to pass the object to it and Rails will extract the data it requires.
For example...
user_path(#user)
edit_user_path(#user)
In the instance of a path, the helper extracts the id of the object; redirect_to extrapolates the functionality to route the request to the show path for that user.
Passing redirect_to accepts an object, invoking the show action for that object.
The reason why this is important is to understand that Ruby (& by virtue Rails) is object orientated.
Object orientated programming means that you should be dealing with objects (not variables).
In the case of Rails, each model should be an object. Every time you load the model, or create a new instance of it, you should be dealing with the object rather than the data.
Therefore, allowing you to pass #objects to methods such as redirect_to is just another way to make Rails more object-orientated.
class SongsController < ApplicationController
def new
#song = Song.new
end
def create
ids_collection = Array.new
# some logic to save multiple songs object and accumulate their ids
# in ids_collection variable.
redirect_to new_song_url, notice: "songs saved"
end
end
I want followings points to be cleared or answered, hoping these are sensible.
1. I want to pass ids_collection array to 'new' method.
2. when I pass like this (i.e. redirect_to new_song_url(ids_collection)) I get, in url like this (i.e. GET "/songs/new.72%2F73") which is hard to decode in new method.
3. I simply want, 'ids_collection' key in params hash and values as array in that key. How can I achieve that and do I need to whitelist 'ids_collection' for strong parameters ?
You will have to pass key, value pair to new_song_url
redirect_to new_song_url(song_ids: ids_collection), notice: "songs saved"
Then you can access song_ids in new action as follows,
params[:song_ids]
Strong parameters deal with mass assignment, not related to what you are trying to do.
Also note that when you access song_ids in new action, they will be of type string. You may want to convert them into integers, params[:song_ids].map(&:to_i)
Suppose i have a model User and a controller UsersController,
in my create actions, i can write
def create
#user = User.new(user_params)
#user.save
redirect_to root_path
end
or
#user = User.new(uer_params)
if #user.save
redirect_to users_path
else
render :new
end
Replicate above 2 actions for Update and destroy also
My question is related to 2nd create action,
Is is necessary to add if else end. what worse could happen i just have create actions like 1st one.
Note: Please ignore the validations part for now.
Just suppose I do not any validations.
What are the other possible conditions in which create/update/destroy will fail apart from validations and which one is the good practice.
Given that you don't want to perform any validations or any checks on the status of the save, then there's no reason for the conditional. In fact, in that case there's also no reason for the #user instance variable. This is all you would need:
def create
User.create(user_params)
redirect_to root_path
end
The conditional is just to do different things based on the status of the save. The instance variable is only to pass the User object to the view. But if you're always doing a redirect then you can't utilize the instance variable anyway, so no need.
What's "right" here is up to the needs of your application. Do the minimum necessary until you have a problem and then fix it.
This:
if User.create(user_params)
is always true. create returns on active reocrd object regardless whether it was successfully created or not. This is why we usually do:
#user = User.new(uer_params)
if #user.save
redirect_to users_path
else
render :new
end
Also note that we are ot redirecting to a new action. The reason is that we already has an #user variable, which 1) holds all the attributes entered by user 2) has all the validation errors attached to it. All we need to do is to render :new template and let Rails do its magic.
Note: If we ignore the validation, then there is no difference which option you will use. You don't need if/else statement neither as it will throw an exception if save fails for any other reason than validation (unless you have after/before_save hooks).
Difference between create & save ?
From the docs:
create
Tries to create a new record with the same scoped attributes defined
in the relation. Returns the initialized object if validation fails
save
.... By default, save always run validations. If any of them fail the
action is cancelled and save returns false. However, if you supply
validate: false, validations are bypassed altogether.
What about validations?
Well,
Create will try saving and returns the initialized object anyway (successful or failed save after validations)
Save will try saving and returns true for successful save and false otherwise
Note that you can skip validations by passing false to save
#user.save(false)
So, what about Conditions?
If you chose to skip validations, using Create or Save(false) then you don't need conditions, while if you need validations then you probably need to check how things went then give user some feedback, hence the conditions
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