简体   繁体   中英

Is it possible to generate a simple_form field out of a JSON response?

I'm searching for a way to create simple_form fields of all options belonging to a room_type, such that a user can fill in the option_quantity. The room_type is asked first in a form and consequently this input needs to be used for the options belonging to that room_type.

Current attempt

Currently, I'm getting al options belonging to this room_type with JQuery, but I don't know how to proceed from here (or maybe there is a better way?)

Response current attempt

The following response is generate using my current attempt.

{rooms: Array(3), options: Array(2), extra_guests: Array(1)}
rooms: (3) [{…}, {…}, {…}]
extra_guests: [{…}]
options: Array(2)
0: {id: 109, room_type_id: 185, name: "Amazing option", description: "", rank: null, …}
1: {id: 110, room_type_id: 185, name: "Second option", description: "", rank: null, …}
length: 2__proto__: Array(0)__proto__: Object

Using this JSON, is it possible to create a separate option field for each option with a field where the user is asked to fill in the option_quantity?

Code

form

<%= simple_form_for [@hotel, @reservation] do |f|%>

<%= f.simple_fields_for :rooms do |room| %>
<%= room.input :room_type, collection: @room_type_list, input_html:{
        id: "room_type"
      }%>
<% end %>

<h4>Options</h4>
<!-- List all options for room_type by name and display field for option_quantity  -->
<% end %>

<script >
  // dynamic options for change category
  $(document).on("change", "#room_type", function(){
    var room_type = $(this).val();

    $.ajax({
      url: "/hotels/<%= @hotel.id %>/reservations/new",
      method: "GET",
      dataType: "json",
      data: {room_type: room_type},
      error: function (xhr, status, error) {
        console.error('AJAX Error: ' + status + error);
      },
      success: function (response) {

      var options = response["options"];
      console.log(response);
      console.log(options)
      console.log($("room_type-options").html(response));
      // Code to generate list of options
    }
  });
  });
</script>

schema

create_table "reservation_options", force: :cascade do |t|
    t.bigint "option_id"
    t.bigint "reservation_id"
    t.integer "option_quantity"
    []
    t.index ["option_id"], name: "index_reservation_options_on_option_id"
    t.index ["reservation_id"], name: "index_reservation_options_on_reservation_id"
  end

create_table "options", force: :cascade do |t|
    t.bigint "room_type_id"
    t.string "name"
    []
    t.index ["room_type_id"], name: "index_options_on_room_type_id"
  end

reservation controller

class ReservationsController < ApplicationController
  # skip_before_action :authenticate_user!
  def new
    @hotel = Hotel.find(params[:hotel_id])
    @reservation = Reservation.new
    @room_type_list = @hotel.room_types
    @all_options = @hotel.options
    @rooms = []
    @options = []
    @extra_guests = []

    # # Display rooms/options for category
    if params[:room_type].present?
      @rooms = RoomType.find(params[:room_type]).rooms
      @options = RoomType.find(params[:room_type]).options
      @extra_guests = RoomType.find(params[:room_type]).extra_guests
    end
    if request.xhr?
      respond_to do |format|
        format.json {
        render json: {rooms: @rooms, options: @options, extra_guests: @extra_guests}
      }
      end
    end

    authorize @reservation
  end

private

def reservation_params
      params.require(:reservation).permit(:arrival, :departure, :payment, :reservation_contact_id, option_ids:[],
      reservation_contact_attributes: [:id, :first_name,
      :last_name, :first_name, :last_name, :zipcode, :city, :street, :street_number,
      :email, :phone, :date_of_birth, :country, :company, :gender, :vat_number],
        rooms_attributes: [:id,:name, :room_type_id,
          room_types_attributes: [:id, :name]],
      reservation_options_attributes: [:id, :option_id, :option_quantity, :_destroy,
        options_attributes: [:id, :name, :room_type_id, :description,
          room_types_attributes:[:id, :name]]],
      reservation_extra_guests_attributes: [:id, :extra_guest_id, :extra_guest_quantity, :_destroy,
        extra_guests_attributes: [:id, :name, :room_type_id, :age_table_id,
          room_types_attributes:[:id, :name]]])
  end

models

class Reservation < ApplicationRecord
  belongs_to :hotel
  belongs_to :room

  has_many :reservation_options, inverse_of: :reservation, dependent: :destroy
  accepts_nested_attributes_for :reservation_options
  has_many :options, through: :reservation_options
end

class Room < ApplicationRecord
  belongs_to :room_type
  validates :name, presence: true
  has_many :reservations, dependent: :destroy
  accepts_nested_attributes_for :room_type
end

class RoomType < ApplicationRecord
  belongs_to :hotel
  has_many :rooms, dependent: :destroy
  accepts_nested_attributes_for :rooms, allow_destroy: true
  has_many :options, dependent: :destroy
  accepts_nested_attributes_for :options, allow_destroy: true
end

class Option < ApplicationRecord
  belongs_to :room_type
  has_many :reservation_options, dependent: :destroy
  has_many :option_prices, dependent: :destroy, inverse_of: :option
end

Outputs responses

console.log(response); =>

{rooms: Array(3), options: Array(2), extra_guests: Array(1)}
rooms: (3) [{…}, {…}, {…}]
extra_guests: [{…}]
options: Array(2)
0: {id: 109, room_type_id: 185, name: "Amazing option", description: "", rank: null, …}
1: {id: 110, room_type_id: 185, name: "Second option", description: "", rank: null, …}
length: 2__proto__: Array(0)__proto__: Object

console.log(options); =>

0: {id: 109, room_type_id: 185, name: "Amazing option", description: "", rank: null, …}
1: {id: 110, room_type_id: 185, name: "Second option", description: "", rank: null, …}
length: 2
__proto__: Array(0) 

console.log($("#room_type-options").html(response)); =>

jQuery.fn.init {context: document, selector: "#room_type-options"}

First, I think I would make my routes look something like:

Rails.application.routes.draw do
  resources :hotels do 
    resources :room_types, shallow: true do 
      scope module: :room_types do 
        resources :options, only: [:index]
      end
    end
  end
 end

Which will give you:

  room_type_options GET    /room_types/:room_type_id/options(.:format)   room_types/options#index
   hotel_room_types GET    /hotels/:hotel_id/room_types(.:format)        room_types#index
                    POST   /hotels/:hotel_id/room_types(.:format)        room_types#create
new_hotel_room_type GET    /hotels/:hotel_id/room_types/new(.:format)    room_types#new
     edit_room_type GET    /room_types/:id/edit(.:format)                room_types#edit
          room_type GET    /room_types/:id(.:format)                     room_types#show
                    PATCH  /room_types/:id(.:format)                     room_types#update
                    PUT    /room_types/:id(.:format)                     room_types#update
                    DELETE /room_types/:id(.:format)                     room_types#destroy
             hotels GET    /hotels(.:format)                             hotels#index
                    POST   /hotels(.:format)                             hotels#create
          new_hotel GET    /hotels/new(.:format)                         hotels#new
         edit_hotel GET    /hotels/:id/edit(.:format)                    hotels#edit
              hotel GET    /hotels/:id(.:format)                         hotels#show
                    PATCH  /hotels/:id(.:format)                         hotels#update
                    PUT    /hotels/:id(.:format)                         hotels#update
                    DELETE /hotels/:id(.:format)                         hotels#destroy

If needed and/or desired, you can limit the generated routes using only: or except: . For instance, you could do:

Rails.application.routes.draw do
  resources :hotels, only: [] do 
    resources :room_types, only: [], shallow: true do 
      scope module: :room_types do 
        resources :options, only: [:index]
      end
    end
  end
end

And get just:

room_type_options GET    /room_types/:room_type_id/options(.:format)   room_types/options#index

You could also get the same thing by doing:

Rails.application.routes.draw do
  get 'room_types/:room_type_id/options', to: 'room_types/options#index'
end

Whichever floats your boat...

Then, I would change my js to something more like:

<%= simple_form_for [@hotel, @reservation] do |f|%>

<%= f.simple_fields_for :rooms do |room| %>
<%= room.input :room_type, collection: @room_type_list, input_html:{
        id: "room_type"
      }%>
<% end %>

<h4>Options</h4>
  <div id="room-type-options">
    <!-- List all options for room_type by name and display field for option_quantity  -->
  </div>
<% end %>

<script >
  // dynamic options for change category
  $(document).on("change", "#room_type", function(){
    var room_type = $(this).val();
    var room_type_id = # do something here...

    $.ajax({
      url: "/room_types/#{room_type_id}/options",
      method: "GET",
      dataType: "json",
      error: function (xhr, status, error) {
        console.error('AJAX Error: ' + status + error);
      },
      success: function (response) {
        console.log(response);
        $("#room-type-options").html(response)
      }
    });
  });
</script>

A few things to note here:

  • You'll need a RoomTypes::OptionsController with an index action
  • The index action should return an html blob of room type options
  • You don't need to pass the hotel_id because a RoomType belongs_to:hotel and the hotel_id , therefore, can be derived (if needed) from the RoomType (thus the shallow: true bit). You can read more about this in the Rails Routing from the Outside In guide
  • You'll need to fill in the var room_type_id = bit on your own. You should be able to get that from the room.input but may need to tweak your code a bit - I'm not sure
  • The $("#room-type-options").html(response) bit is directionally correct, but may need to be fiddled with because I forget exactly what the response structure is
  • That <div id="room-type-options"> bit may or may not be correct. I haven't hand-crafted html in I don't know how long. (I use haml.)

You can implement the RoomTypes::OptionsController by creating an options_controller.rb file in a app/controllers/room_types folder. It would look like any other controller:

# app/controllers/room_types/options_controller.rb
class RoomTypes::OptionsController < ApplicationController

  def index
    @room_type = RoomType.find_by(params[:room_type_id])
    ... do some stuff ...
    ... render some html ...
    ... do a happy dance ...
  end

end

Because this controller is namespaced, you can have it at the same time as you have your regular OptionsController and RoomTypesController and there won't be any conflict.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM