Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails - How to avoid using hidden_fields in the view to pass values to controller?

Is there a way I can avoid the hidden_field method of passing values in the view to a controller? I would prefer a controller method for security reasons. Unfortunately value pairing @variables is not supported in strong_parameters.

EDIT 6/18 1:00 PM EST

  1. I've renamed my garages controller to appointments
  2. cars_controller no longer creates a new appointment (formally garages). A new appointment is created in the appointments_controller

My current structure


routes

Rails.application.routes.draw do

resources :techs, only: [:index, :show], shallow: true do
    resources :cars, only: [:new, :create]
end

resources :appointments

#For searchkick
resources :cars, only: [:show] do
    collection do
        get 'search'
    end
end

root "home#index"

end

models

tech.rb

class Tech < ActiveRecord::Base
    searchkick

    has_many :appointments
    has_many :customers, :through => :appointments
    has_many :service_menus
    has_many :services
    has_many :cars
end

service.rb

class Service < ActiveRecord::Base
    belongs_to :tech
    belongs_to :service_menu

    has_many :cars, dependent: :destroy
    accepts_nested_attributes_for :cars, :reject_if => :all_blank, :allow_destroy => true
end

car.rb

class Car < ActiveRecord::Base  
  belongs_to :service
  belongs_to :tech
  has_many :appointments 
end

appointment.rb

class Garage < ActiveRecord::Base
  belongs_to :customer
  belongs_to :tech
  belongs_to :car
end

controllers

cars_controller

def new
  @car = Car.find(params[:id])
  @tech = Tech.find(params[:tech_id])

  @appointment = Garage.new 
end

appointments_controller

def create
  @appointment = current_customer.appointments.build(appointment_params)

  if @appointment.save
    redirect_to appointments_path, notice:  "You car has been added to this appointment." 
  else
    redirect_to appointments_path, notice:  "Uh oh, an error has occured." 
  end
end

private

def appointment_params
  params.require(:appointment).permit(:tech_id, :service_id, :car_id, ...and a bunch of other keys here)
end

views

cars.new.html

Please note this form passes hidden values to the appointment_controller.

Value from @car.name and other alike are not from a text_field but rather a pre-defined value based on selections from a previous page which is store in the cars db.

<%= simple_form_for(@appointment, { class: 'form-horizontal' }) do |f| %>
        <%= f.hidden_field :tech_id, value: @tech.id %>
        <%= f.hidden_field :car_id, value: @car.id %>
        <%= f.hidden_field :service_id, value: @car.service.id %>
        <%= f.hidden_field :customer_car, value: current_customer.car %>
        <%= f.hidden_field :customer_street_address, value: current_customer.street_address %>
        <%= f.hidden_field :customer_city, value: current_customer.city %>
        <%= f.hidden_field :customer_state, value: current_customer.state %>
        <%= f.hidden_field :customer_zip_code, value: current_customer.zip_code %>
        <%= f.hidden_field :service_name, value: @car.service.service_menu.name %>    
        <%= f.hidden_field :car_name, value: @car.name %>
    
        <%= **And a bunch of other hidden values here which are too long to list**  %>
    
    <%= f.submit "Add to appointment", class: 'btn btn-default' %>
    <% end %>

service.html

<%= render 'form' %>

_form.html

<%= simple_form_for @service do |f| %>
  <div class="field">

    <%= f.label "Select service category" %>
    <br>
    
    <%= collection_select(:service, :service_menu_id, ServiceMenu.all, :id, :name, {:prompt => true }) %>

    <%= f.fields_for :cars do |task| %>
      <%= render 'car_fields', :f => task %>
    <% end %>
  </div>

  <div class="links">
    <%= link_to_add_association 'Add New Car', f, :cars, class: 'btn btn-default' %>
  </div><br>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

_car_fields.html

<div class="nested-fields">
  <div class="field">
        <%= f.label :name %><br>
        <%= f.text_field :name %><br>
        <%= f.label :hours %>
        <%= f.select :hours, '0'..'8' %>
        <%= f.label :minutes %>
        <%= f.select :minutes, options_for_select( (0..45).step(15), selected: f.object.minutes) %><br>
        <%= f.label :price %><br>
        <%= f.text_field :price, :value => (number_with_precision(f.object.price, :precision => 2) || 0) %> <br>
        <%= f.label :details %><br>
        <%= f.text_area :details %></div>
  <%= link_to_remove_association "Remove Car", f, class: 'btn btn-default' %>
  <%= f.hidden_field :tech_id, value: current_tech.id %>
  <br>
    <hr>
</div>

> Edit 7/14 1:30 pm EST

Brief Synopsis on this specific function of the application

  1. A customer clicks through a list of services a tech has to offer
  2. The customer selects a service for example brakes which is a service a tech has listed in his profile.
  3. The attributes for brakes are listed in the cars db
  4. cars belongs_to to techs
  5. The customer can save brakes which is an attribribute of a techs car to a appointment
  6. A good number of predefined values from tech, the customer's street address, etc..., and the car are pre-loaded in the form for storing in the appointments table.
  7. appointment acts as a histories table. So if the tech decides to modify any one of his services in this example brakes, the appointments tables will remain untouched for the brakes entry.
  8. Once the customer selects the Add to appointment button, it will save all of the predefined values from tech, customer, and car attributes (in this example brakes) to the appointments db.

Another approach to this would be to get rid of the strong parameters altogether and do the following:

def create
    @appointment = Garage.create(tech_id: @car.service.tech.id,
                                      customer_id: current_customer.id,
                                      customer_street_address: current_customer.street_address,
                                      customer_city: current_customer.city,
                                      customer_state: current_customer.state,
                                      customer_zip_code: current_customer.zip_code,
                                      customer_phone_number: current_customer.phone_number,
                                      customer_location_type: "WILL ADD LATER",
                                      customer_latitude: current_customer.latitude,
                                      customer_longitude: current_customer.longitude,                                      
                                      service_id: @car.service.id,
                                      service_name: @car.service.name,
                                      car_id: @car.id,
                                      car_name: @car.name,
                                      car_time_duration: @car.time_duration,
                                      price: @car.price,
                                      car_details: @car.details)
  
    if @appointment.save
      redirect_to techs_path, notice:  "This service has been saved." 
    elsif 
      redirect_to tech_path, notice:  "Uh oh, an error has occurred." 
    end
  end

Please let me know if you require further details.

like image 648
captain awesome Avatar asked Jul 12 '15 16:07

captain awesome


2 Answers

I can think of some methods you could use to avoid this form bloated with hidden_fields:

  • Share data between controllers in the user's session, pretty much like a shopping cart in an e-commerce application.
  • If you prefer to preserve the statelessness of the application, create a model to temporarily store these informations; this way you'll only need to include one hidden_field in the form.
  • Use JavaScript to make the requests, storing the data in local objects and passing them as JSON when needed (this is trivial using AngularJS).

Whichever method you choose, keep in mind that storing a lot of state in a web application usually is a code smell. You can always rethink your application so you don't need to keep so much context.

like image 148
mrodrigues Avatar answered Nov 08 '22 12:11

mrodrigues


To resolve my issue, my latest edit from my initial post stated the following:

EDIT 6/18 1:00 PM EST

  1. I've renamed my garages_controller to appointments_controller
  2. cars_controller no longer creates a new appointment (formally garages). A new appointment is created in the appointments_controller

Only hidden_field i'm passing is the car_id in the appointments view /new.html.erb <%= f.hidden_field :car_id, value: @car.id %>.

In the appointments_controller, I'm assigning all the car attributes doing the following.

def create
    @appointment = current_customer.appointments.build(appointment_params)
    @appointment.tech_id = @appointment.car.service.tech.id
    @appointment.price = @appointment.car.price
    @appointment.car_name = @appointment.car.name
    @appointment.car_details = @appointment.car.details

    if @appointment.save
        redirect_to appointments_path, notice:  "Thank you booking your appointment." 
    else
        redirect_to appointments_path, notice:  "Uh oh, an error has occurred. Please try again or contact us for further assistance" 
    end
end

Thank you all for your responses. I should've known better. :(

like image 33
captain awesome Avatar answered Nov 08 '22 14:11

captain awesome