Set a field value outside a form in Rails - ruby-on-rails

So I have a Rails app with a Post model. One of the fields is collection_id.
Each post should have the ID of the latest collection at the time of posting. If I have that ID in the backend, and I remove the form field for collection_id, how can I make sure this ID goes into the database without having a hidden_field in the Rails frontend?
The problem with a hidden field is that users could use a web inspector to change the value. This needs to be secure.
What's the best way to do this?

If you have that ID in your backend, you can pass it in your controllers action before saving:
#post = Post.create(params[:post])
#post.controller_id = variable_holding_the_id
if #post.save ...
or in some cases you can do it in the model with a callback:
after_create :set_collection_id
def set_collection_id
self.collection_id = variable_holding_the_id
end

Related

Rails 5.2: secure form_for to prevent user to alter form select_tag / hidden_field values

In my form I'm using authenticity_token: true and I'm wondering if this is enough to prevent current_user to alter given options for my select_tag? Same question applies for using hidden_field in my form.
In my case there is a form, where current_user can create User and add it to Company. current_user can select its own companies, e.g., current_user.companies.order(:name), however I'm worried current_user could brake my form and pass in ID of Company, which doesn't belongs to him. Basically in that way current_user can become User of foreign Company and then do nasty things...
So far I've been reading https://guides.rubyonrails.org/security.html and maybe have not noticed some important info there. I'd be happy to know more any security measures I can take to make my form more secure. Thank you.
Usually, this kind of problem arises when you want to validate the record before associating.
The classic solution is to validate them on the server side after submission of the form.
For example, if you only want to create an associated object with user and you are passing user_id as hidden_field. It is better to create the object directly against current user hence avoiding any manipulation of hidden fields.
Instead of
Article.create(article_params) # which includes user_id provided as hidden field
current_user.articles.create(article_params) # no need of user_id
So, In your case before creating the new user, you can check something like this
user = User.new(params) # remove company_id from here
user.company = current_user.companies.find_by(id: params[:user_params][:company_id]) # This will set company to `nil` if the company is not associated with current user
user.save

Rails 5 - Best way to hold onto params for creating associations?

Here's the scenario to illustrate my question. I have 2 models:
# models/post.rb
belongs_to :user
validates_presence_of :comment
And we have a devise model called Users
# models/user.rb
has_many :posts
What I would like to achieve:
Person comes to the website, is able to create a Post, after creating the Post, they are prompted to create an account. After creating the account, the Post that they just created would be associated to the User they just created.
Usually i'd make use of routes to hold the params[:id] which can be accessed in the controller method. For example the URL may look something like this:
www.foo.com/foo/new/1
And then I can do this:
# foo_controller.rb
def new
#foo = Foo.new
#parent = Parent.find(params[:id])
end
And in the view I can simply access #parent and use a hidden field to fill the parent ID.
But when routing through so many different pages (such as creating a Devise User), how do I hold onto the parent/child ID such that I can still create that association?
Using an hidden field or the route to store the id, with no authorization in the process, would not be secure. What if I just use the browser inspector and change the value of the id ? Your cool post would be mine.
What you could do is, for instance, add a field called guest_id to the Post, in which the value is unique (like SecureRandom.uuid), and also store that value in the session.
Thus, after the user is created, you could do something like that
if (post = Post.find_by(guest_id: session[:guest_id])).present?
post.update(user_id: current_user.id)
end

How to correctly pass through foreign keys in Rails form_for

I currently have a working form to create a resource (An event booking) which belongs_to two other models, a Consumer (the customer) and a Course. In the Booking creation form, I'm using two hidden fields which pass through consumer_id and course_id.
For this to work in form_for, I've created two virtual attributes in my Booking model
attr_accessor :course_id, :consumer_id
And in the create event of BookingsController, I've grabbed those ID's from mass assignment and then manually assigned the actual Course and Consumer objects from the ID
bookings_controller.rb
def create
#booking = Booking.new(booking_params)
#booking.course = Course.find(#booking.course_id)
#booking.consumer = Consumer.find(#booking.consumer_id)
if #booking.save_with_payment
# Payment was successful, redirect to users account page to view it and past bookings
else
render :new
end
end
private
def booking_params
params.require(:booking).permit(:course_id, :consumer_id, :card_token, :visible, :created_at)
end
Is this best practice? I tried to name the form hidden fields as consumer and course, hoping that Rails would see that the value is an ID and automatically do a .find for me, but that doesn't appear to be the case. I'll be surprised if Rails can't take care of this automatically, I'm just not sure how to accomplish it.
It's simpler than you imagine and you're already most of the way there.
When you create a booking, you need only to set the course_id and consumer_id fields, so make sure you've got hidden fields set up in your form with these names and the right values:
<%= f.hidden_field :course_id, value: my_course_id %>
<%= f.hidden_field :consumer_id, value: my_consumer_id %>
Don't set course or consumer in your controller or in your form. That is, remove the following lines from your controller:
#booking.course = Course.find(#booking.course_id)
#booking.consumer = Consumer.find(#booking.consumer_id)
You already have course_id and consumer_id in your permit list, so when you post the form, the values for those parameters will be set on your new booking, which is all that you should care about.
When you attempt to access #booking.course, ActiveRecord will do a find for you based on the id set in course_id; this is handled by the belongs_to association that you've established in your model.

What happens when params has more parameters than table columns

In rails this type code automatically generated
#post = Post.new(params[:article_post])
#post.save
What happens when there are more parameters than database table columns? Say in database table we have column post_name post_id and in form I have a checkbox also along with another input field which do not need to save in database table but need for validation. In this case how the above code works. I want to know the basics.
Thanks
#post = Post.new(params[:article_post])
#post.save
Rails accepts only those parameters from request that are matched with table attributes.
For your check box validation, you can check manually like :
if params[:check_box_attributes_name]
#post = Post.new(params[:article_post])
#post.save
end
#post = Post.new(params[:article_post])
#post.save
This code save the columns which are matched with model attributes and assign them with given values and save in the database, And columns which are posted from form but didn't have matched attributes in database will not affect the code.
I assume you use Rails 3, since Rails 4 has different behavior. When you send new, create, attributes= or some other messages and pass a hash (and params[:article_post] is a hash), rails internally iterates through the hash and calls #{param_name}= method on model object. That is,
Post.new(:name => 'hello', :something_not_in_db => "Amazing!")
Is equivalent to
post = Post.new
post.name= 'hello'
post.something_not_in_db= "Amazing!"
Actually, rails first checks that all options that you pass in hash are allowed to be set with attr_accessible. But then it does not matter if your model table has column or not, it only matters that is responds to attribute_name= message

Rails: Associate posts with user and location

I have a Post model in my app, which belongs_toa User model, as well as a Location model. Both the User and Location models use the has_many relation with Post.
Now, in my Post controller's create action, I want to automatically associate the Post with the currently logged in user (available to me as current_user through Devise).
Also, it should associate with a pre-existing Location if one exists with the same address field (which he enters through the form), or create a new one and associate it with that if not.
My Post model has a user_id field as well a location_id field for this purpose.
How do I accomplish the two associations automatically in the create action when the user creates a new Post?
You'll need to use 2 statements.
#post = current_user.posts.build(params[:post])
#post.location_id = params[:location_id] # change this to whatever you're passing.
#post.save

Resources