Rails 5: insert name in form and save id - ruby-on-rails

I have 4 tables:users, observative_session, observations, celestial_bodies.
Each user has many observative_session and each observative_session has many observations. I already put in the model the associations
So in observative_session I have a foreign_key (user_id) to link it to the user and in observation I have one foreign key (user_id) for the user and a foreign key (observative_session_id) for the observative_session plus another foreign key for the celestial_body (celestial_body_id).
I created a form in which I ask the user to insert the name of a celestial body
<%= f.text_field :celestial_body_id, label: 'Celestial body' %>
but I can't save the string as an id so I need to find the id corresponding to the inserted body and save it instead.
I tryed to define a virtual attribute
def celestial_body_name
CelestialBody.where(' ')
end
def celestial_body_name= (name)
celestyal_body_id = CelestialBody.where(name: celestial_body_name)
end
and then I create the new observation
def create
#observation = #observative_session.observations.build(observation_params)
....
end
but I get the undefined method 'observations' for nil:NilClass
I don't understand if I pass the parameters correctly or not.
Thank you for any help.

You should go with some autocomplete solution as https://github.com/bigtunacan/rails-jquery-autocomplete. You need to think about passing additional hidden field as celestial_body_id to create valid association in controller. I think this part should help you -- https://github.com/bigtunacan/rails-jquery-autocomplete#sending-extra-search-fields.
Of course you can still pass name without autocomplete, but it's bad for UI (you never know whether such category exists and you can even make some typo in it) and it will require additional queries on logic on backend side

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

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.

Mass assignment with update_attributes does not update collection_singular_ids

I have a simple User model which is associated to many Town objects using a join table (has_and_belongs_to_many). Now I'd like to update the towns belonging to a particular user by assigning a list of comma-separated town ids (coming directly from the form sent as a HTTP POST parameter).
The user object is saved using the following controller code:
#current_object.update_attributes(params[:user])
The params[:user] includes town_ids which is, for example, set to 1,4,6.
Unfortunately, this does not update the user-town associations at all. However, if I do it manually, it works beautifully well:
User.find(:first).town_ids = "1,4,6" # this saves automatically
Could it just be that it is not possible to mass-assign these collection_singular_ids fields?
My user model contains the following:
has_and_belongs_to_many :towns
# necessary for mass-assignment, otherwise it results in an exception:
attr_accessible :town_ids
Any help is greatly appreciated.
You have to pass the town_ids as an array:
User.find(:first).update_attributes(:town_ids=>[1,4,6])
If you pass the ids as a string Rails will attempt to convert the string to an integer:
"1,4,6".to_i # => 1

simple_form send a extra parameter

I am using https://github.com/plataformatec/simple_form and am trying to send a extra parameter. I have a Task, List and ListTask models, in the new page of the of the list I want to be able to insert the number of tasks that will be added. When you submit it will send you to the new list_task page with the correct number of forms populated.
=simple_form_for #list do |s|
=s.input :title
=s.input :task_count
=s.button :submit
This produces a error undefined method task_count, which makes sense because it is not a method in list.
if you don't want the value persisting to the db, add a virtual attribute:
class List < ActiveRecord::Base
attr_accessor :task_count
end
this will allow you to use that attribute in the form, but that will only persist for the life of that object but it will make it into your POST params.
...otherwise, if you want it to persist to the db (which is sounds like you may). You'd add task_count as a column in your lists table (via a migration).

Rails Foreign Key Issue

Sorry if this question seems simple, I am very very new to Rails (just started learning a few days ago), but after consulting Google and "Agile Web Development with Rails" I can't find the answer.
I have an issue with Rails 2.3.8 creating a foreign key on two models. My tables look like this:
cars manufacturer
---- ------------
car_make name
car_model country
car_class logo_url
image_url (and default 'id' created by Rails)
manufacturer_id
(and default 'id' created by Rails)
My 'car_make' and 'name' fields are essentially the same; every Car I create, I want to be able to associate it with an existing Manufacturer. This is the column I am trying to create FK on.
My car.rb has 'belongs_to :manufacturer', and my manufacturer.rb has 'has_many :cars' to establish a one manufacturer to many cars relationship. However, when I create a new car (via scaffolding) the manufacturer_id field is blank.
I went to my cars_controller, found the 'create' method that is being used, and tried to add the second line below:
#car = Car.new(params[:car])
#car.manufacturer_id = car.manufacturer.id # <===
This produces a 'NameError in CarsController#create' error, and I see:
undefined local variable or method 'car' for #<CarsController:0x1034642f0>
Rails doesn't seem to like the line I've added. What am I missing to make this work?
Well, you need to have a manufacturer available before you can attach it to the car.
#car = Car.new( params[:car] )
m = Manufacturer.first # => as you can see you must already have one made
#car.manufacturer = m
#car.save
The reason car is undefined is because, well, you haven't defined it. Which car's manufacturer did you want to assign to #car?
So basically you need to make a manufacturer before you make a car. If the form you're filling out has the data for the manufacturer then make sure to put that under a different key in params, like, say, params[:manufacturer] and do a similar thing as you're doing with the car. Maybe like:
#car = Car.new( params[:car] )
#manufacturer = Manufacturer.find_or_create_by_name_and_country( params[:manufacturer][:name], params[:manufacturer][:country] )
#car.manufacturer = #manufacturer
#car.save
In your view, you want to generate a drop-down list for manufacturers (I would assume), so you should do something like this in the form:
<%= collection_select(:car, :manufacturer_id, Manufacturer.all, :id, :name) %>
Then your create action shouldn't need to explicitly set a manufacturer_id because it should have received that from the form.

Resources