Im building an app where users can publish there Real Estate properties. So I have two tables one that is called Property and the other called Amenity(for icons likes bathrooms, pool, and etc.) I made the Amenity table separated from the Property table so I can use it with other tables and I have this error Unpermitted parameter::gym
So this is my code:
property.rb model
class Property < ApplicationRecord
belongs_to :owner
has_many :amenities
accepts_nested_attributes_for :amenities
end
amenity.rb model
class Amenity < ApplicationRecord
belongs_to :property
end
properties_controller.rb
class PropertiesController < ApplicationController
before_action :set_property, only: [:show, :edit, :update, :destroy]
before_action :authenticate_owner!
.
.
.
# POST /properties
# POST /properties.json
def create
#property = current_owner.properties.new(property_params)
respond_to do |format|
if #property.save
format.html { redirect_to #property, notice: 'Tu propiedad ha sido creada!' }
format.json { render :show, status: :created, location: #property }
else
format.html { render :new }
format.json { render json: #property.errors, status: :unprocessable_entity }
end
end
end
.
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_property
#property = Property.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def property_params
params.require(:property).permit(:name, :description, :price, amenities_attributes: [:id, :bathroom, :room, :pool, :gym,
:kitchen, :terrace, :balcony, :living_room, :garage, :parking_lot, :green_area])
end
end
amenities migration table
class CreateAmenities < ActiveRecord::Migration[5.2]
def change
create_table :amenities do |t|
t.integer :bathroom
t.integer :room
t.integer :pool
t.integer :gym
t.integer :kitchen
t.integer :terrace
t.integer :balcony
t.integer :living_room
t.integer :garage
t.integer :parking_lot
t.integer :green_areas
t.references :property
t.timestamps
end
add_index :amenities, [:id, :created_at]
end
end
properties migration table
class CreateProperties < ActiveRecord::Migration[5.2]
def change
create_table :properties do |t|
t.string :name
t.text :description
t.integer :price
t.string :services
t.string :rules
t.string :address
t.float :latitude
t.float :longitude
t.references :owner
t.timestamps
end
add_index :properties, [:id, :rfc, :created_at]
end
end
Console logs
Parameters: {"utf8"=>"✓", "authenticity_token"=>"GjmTFKS3cQRwgrSnTLFOoWQV/gXdTgST0nf7GOs7ZS2i8wneFqzADeTLUo26UKkA5392nrDKGZpVyav4LWpfjw==", "property"=>{"name"=>"Propiedad1", "description"=>"Propiedad1", "price"=>"120000", "gym"=>"1"}, "commit"=>"Create Property"}
Owner Load (0.3ms) SELECT "owners".* FROM "owners" WHERE "owners"."id" = ? ORDER BY "owners"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ /Users/kensanchez/.rvm/gems/ruby-2.5.3/gems/activerecord-5.2.4.1/lib/active_record/log_subscriber.rb:98
Unpermitted parameter: :gym
As im concerned this has to work fine, but im having some issues understanding it. I will appreciate your help guys! Thanks.
EDIT:
My web form
<%= form_with(model: property, local: true) do |form| %>
<% if property.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(property.errors.count, "error") %> prohibited this property from being saved:</h2>
<ul>
<% property.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="container">
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
.
.
.
<!--Gym attribute from amenities-->
<div class="field">
<%= form.label :gym %>
<%= form.number_field :gym %>
</div>
<div class="actions">
<%= form.submit %>
</div>
</div>
<% end %>
This part of your parameters "property"=>{"name"=>"Propiedad1", "description"=>"Propiedad1", "price"=>"120000", "gym"=>"1"} should has the same structure as it is in property_params.
The parameter gym has to be inside amenities_attributes.
Like this: "property"=>{"name"=>"Propiedad1", "description"=>"Propiedad1", "price"=>"120000", "amenities_attributes" => [{ "gym"=>"1" }]}.
UPD
Check this out https://guides.rubyonrails.org/form_helpers.html#nested-forms
Try to use this piece of code in the form view:
<!--Gym attribute from amenities-->
<%= form.fields_for :amenities do |amenities_form| %>
<div class="field">
<%= amenities_form.label :gym %>
<%= amenities_form.number_field :gym %>
</div>
<% end %>
This what I am seeing in your console logs output
"property"=>{"name"=>"Propiedad1", "description"=>"Propiedad1", "price"=>"120000", "gym"=>"1"}
These are params for a property and the last value is "gym"=>"1", this is the reason you are getting unpermitted parameter.
It should appear under amenities_attributes like
"property"=>{"name"=>"Propiedad1", "description"=>"Propiedad1", "price"=>"120000"}, "amenities_attributes": [{ "gym"=>"1" }] }
Related
This is an extension of this original question: Rails - Editing User and Profile Models from separate Settings Controller
My form works perfectly for editing a single model (Profile), however I have attempted to extend this to also allow for a user to edit some of the fields from the User model. Currently, the entire form is no longer saving any data - but I am not seeing any visible error messages in browser, other than the "success" message in my update method is not firing.
How can I successfully extend this setup to allow for both User and Profile fields to be saved in the same form? The form currently edits a Profile, and then allows fields_for a user - is this the wrong way around?
I have 2 models, User:
class User < ApplicationRecord
has_one :profile, dependent: :destroy
before_create :create_profile
private
def create_profile
build_profile(name: username)
end
end
and Profile:
class Profile < ApplicationRecord
belongs_to :user
accepts_nested_attributes_for :user
end
Both models are editable via the SettingsController:
class SettingsController < ApplicationController
def profile
#profile = User.find_by_id(current_user).profile
end
def update
set_profile
respond_to do |format|
if #profile.update(profile_params)
format.html { redirect_back fallback_location: settings_path, notice: 'Profile was successfully updated.' }
else
format.html { render :edit }
end
end
end
private
def profile_params
params.require(:profile).permit(:name, user_attributes: [:email])
end
end
On settings/profile, a user's profile is editable with the following form:
<h1>Settings</h1>
<div>
<div>
Name: <%= #profile.name %>
</div>
<%= form_with(model: #profile, url: update_settings_profile_path, local: true) do |form| %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<%= form.fields_for :user do |user_form| %>
<div class="field">
<%= user_form.label :email %>
<%= user_form.text_field :email %>
</div>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
</div>
Are here are the list of routes that show the profile page, and the update method for all of the other methods:
get 'settings', to: redirect('settings/profile')
get 'settings/profile', to: 'settings#profile', as: :settings_profile
patch 'settings', to: 'settings#update', as: :update_settings
Parameters when the form is submitted: (Removed auth token for clarity.)
Parameters: {"utf8"=>"✓", "authenticity_token"=>"X", "profile"=>{"name"=>"John Doe", "user_attributes"=>{"email"=>"test#email.com", "id"=>"22"}}, "commit"=>"Update Profile"}
Schema: (Basic columns removed for clarity.)
create_table "profiles", force: :cascade do |t|
t.string "name"
t.bigint "user_id"
...
t.index ["user_id"], name: "index_profiles_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "username", default: "", null: false
...
end
Appreciate any tips!
I try to make a connection between a Match and 2 Players in a form where we can select the players from the list of the players registered:
<%= f.label :"Joueur 1" %>
<%= f.select :playerone, #players.collect {|a| [a.name, a.id]} , class: 'form-control' %>
<%= f.label :"Joueur 2" %>
<%= f.select :playertwo, #players.collect {|b| [b.name, b.id]} , class: 'form-control' %>
<%= f.label :Prolongations %>
<%= f.check_box :prolongations %><br />
<%= f.submit yield(:button_text), class: "btn btn-primary" %>
The schema of tables : with a Join Table Matches Players
create_table "matches", force: :cascade do |t|
t.boolean "prolongations"
end
create_table "matches_players", id: false, force: :cascade do |t|
t.integer "match_id", null: false
t.integer "player_id", null: false
t.index ["match_id", "player_id"], name: "index_matches_players_on_match_id_and_player_id"
t.index ["player_id", "match_id"], name: "index_matches_players_on_player_id_and_match_id"
end
create_table "players", force: :cascade do |t|
t.string "name"
t.integer "points"
end
In matches.controller :
class MatchesController < ApplicationController
attr_accessor :player_id, :playerone, :playertwo
def new
#match= Match.new
#players = Player.all
end
def create
#match = Match.new(match_params)
#players = Player.all
if #match.save
flash[:success] = "Votre match a bien été enregistré !"
redirect_to #match
else
render 'new'
p "Une erreur existe, veuillez recommencer."
end
end
def show
#match = Match.find(params[:id])
end
private
def match_params
params.require(:match).permit(:prolongations, :playerone, :playertwo)
end
end
And in the Match model :
class Match < ApplicationRecord
has_many :teams , class_name: "Team"
belongs_to :playerone, class_name: "Player" ,foreign_key: "player_id"
belongs_to :playertwo, class_name: "Player" ,foreign_key: "player_id"
end
And the result when I submit my form is :
Player(#69852298534200) expected, got "1" which is an instance of String(#4817000)
{"utf8"=>"✓",
"authenticity_token"=>".............",
"match"=>{"playerone"=>"1", "playertwo"=>"3", "prolongations"=>"0"},
"commit"=>"Enregistrer le match"}
How can I solve it ?
The easiest fix is just to use playerone_id and playertwo_id as the param keys (change the name of the inputs). If you use playerone the setter expects the argument to be an instance of Player - not a string containing an id.
Which will fix the immediate issue but there is a much better way to solve it.
Start by setting up a real many to many association:
class Match < ApplicationRecord
has_many :player_matches, dependent: :destroy
has_many :players, through: :player_matches
end
class Player < ApplicationRecord
has_many :player_matches, dependent: :destroy
has_many :matches, through: :player_matches
end
class PlayerMatches < ApplicationRecord
belongs_to :player
belongs_to :match
end
This lets you avoid a very awkward issue when you set two belongs to associations to the same table where the associated record can be in either column:
Match.find_by("(playerone_id = :a OR playertwo_id = :a) AND (playerone_id = :b OR playertwo_id = :b)", a: a, b: b)
Yeah thats how you have to query for a match between players. Joins can be even more messy.
With that setup you can simply assign players to a match by:
#match.player_ids = [1,2]
Which is exactly what you can do with the collection helpers:
<%= form_for(#match) do |f| %>
<div class="field">
<%= f.label :player_ids, "Select the players" %>
<%= f.collection_select :player_ids, Player.all, :id, :name, multiple: true %>
</div>
<% end %>
All you have to do on the controller side is whitelist player_ids:
class MatchesController < ApplicationController
# ...
def create
#match = Match.new(match_params)
# ...
end
def update
if #match.update(match_params)
# ...
else
# ...
end
end
# ...
private
def match_params
params.require(:match).permit(:foo, :bar, player_ids: [])
end
end
I've already searched about the whole internet but I can't get this forms working. Here's the thing:
I have two models connected by a has_many :through join model:
estoque.rb
class Estoque < ActiveRecord::Base
has_many :mpm_ests
has_many :ordem_de_servicos, :through => :mpm_ests, dependent: :destroy
end
ordem_de_servico.rb
class OrdemDeServico < ActiveRecord::Base
has_many :mpm_ests, dependent: :destroy
has_many :estoques, :through => :mpm_ests
accepts_nested_attributes_for :mpm_ests
end
And the join model mpm_est.rb
class MpmEst < ActiveRecord::Base
belongs_to :ordem_de_servico
belongs_to :estoque
end
What I want to do is make a collection_check_boxes with a nested extra text_field called quantidade (quantity), as I've setup the join table:
migration file of the join table (mpm_est):
class CreateMpmEsts < ActiveRecord::Migration
def change
create_table :mpm_ests do |t|
t.integer :ordem_de_servico_id
t.integer :estoque_id
t.string :quantidade
end
add_index :mpm_ests, :ordem_de_servico_id
add_index :mpm_ests, :estoque_id
add_index :mpm_ests, [:ordem_de_servico_id, :estoque_id], unique: true
end
end
But the problem is I have no idea how to do this in my controller and view. I've tried something like this, but it didn't work.
ordem_de_servicos_controller.rb
def new
#ordem_de_servico = OrdemDeServico.new
#ordem_de_servico.mpm_ests.build
end
def edit
#ordem_de_servico.mpm_servs.build
#ordem_de_servico.mpm_ests.build
end
[...]
def ordem_de_servico_params
params.require(:ordem_de_servico).permit(:cliente_id, :veiculo, :placa, :mecanico_id, {:estoque_ids => []}, :quantidade, :prazo, :pago, :valor_pago, :historico_pgto, :status)
end
and in my ordem_de_servico _form view:
<%= f.fields_for :mpm_ests do |ff| %>
<%= ff.collection_check_boxes(:ordem_de_servico, :estoque_ids, Estoque.all, :id, :nome) %>
<%= ff.text_field :quantidade %><br>
<% end %>
Edit 1
The basic idea what I want to do is something like this:
<!DOCTYPE html>
<html>
<body>
<h1>Ordem De Servico (Service)</h1>
<label>Number<label>
<input type="text">
<label>Service<label>
<input type="text">
<label>Person<label>
<input type="text">
<h5>Inventory (estoque)</h5>
<form action="">
<input type="checkbox" name="vehicle" value="Bike">Iron <label>Quantity<label><input type="text"><br>
<input type="checkbox" name="vehicle" value="Car">copper <label>Quantity<label><input type="text"><br>
<br><button>Save Ordem de Servico (service)</button>
</form>
</body>
</html>
I don't say this is complete answer but I think you are trying to save selected check boxes using nested attributes and collection_check_boxes. It will basically send array of estoque_ids, if thats the case, below code will work and you can use it as reference to modify further.
Migration (specify type as array for estoque_ids)
class CreateMpmEsts < ActiveRecord::Migration
def change
create_table :mpm_ests do |t|
t.integer :ordem_de_servico_id
t.string :quantidade
t.text :estoque_ids, array: true, default: []
t.timestamps null: false
end
end
end
form (New Ordem De Servico)
<%= f.fields_for :mpm_ests do |ff| %>
<%= ff.collection_check_boxes(:estoque_ids, Estoque.all, :id, :name) do |b|
b.label { b.check_box }
end
%>
<%= ff.text_field :quantidade %><br>
<% end %>
Controller (Permit params as required)
def create
#ordem_de_servico = OrdemDeServico.new(ordem_de_servico_params)
respond_to do |format|
if #ordem_de_servico.save
format.html { redirect_to #ordem_de_servico, notice: 'Ordem de servico was successfully created.' }
format.json { render :show, status: :created, location: #ordem_de_servico }
else
format.html { render :new }
format.json { render json: #ordem_de_servico.errors, status: :unprocessable_entity }
end
end
end
private
def ordem_de_servico_params
params.require(:ordem_de_servico).permit! #permit params u need
end
EDIT 1:
<div class="field">
<% Estoque.all.each_with_index do |es,index| %>
<div>
<%= check_box_tag "ordem_de_servico[mpm_ests_attributes][#{index}][estoque_id]", es.id%>
<%= es.name %>
<%= text_field_tag "ordem_de_servico[mpm_ests_attributes][#{index}][quantidade]", :quantidade %><br>
</div>
<% end %>
</div>
I have a Rails 4.2 app which has 'Rooms', 'Bookings' and 'Extras'.
When making a booking it is for a room e.g. website.com/rooms/1/bookings/1
I have extras which I want to be associated with the booking for that room via check-boxes.
How can this be implemented? I've been reading about has_many :foo, :through => :bar associations but I'm not sure if that's the way to go.
The relevant code looks like this:
<!-- app\views\bookings\_form.html.erb -->
<%= form_for([#room, #booking]) do |f| %>
<p>
<%= f.label 'Select Customer:' %>
<%= f.collection_select :user_id, User.all, :id, :customer_name %>
</p>
<p>
<%= f.label 'start_time', 'Start Date and Time:' %>
<%= f.datetime_select :start_time, { minute_step: 15 } %>
</p>
<p>
<%= f.label 'length', 'Length of booking in hours:' %>
<%= f.number_field 'length', min: 1 %>
</p>
<p>
<%= f.label 'Room Price:' %>
<%= number_to_currency #room.price, unit: "£" %>
</p>
<p>
<%= f.label 'Extras:' %>
<%= f.collection_check_boxes :extra_ids, Extra.all, :id, :extra_info %>
</p>
<%= f.submit 'Submit' %>
<% end %>
# app\models\booking.rb
class Booking < ActiveRecord::Base
belongs_to :room
belongs_to :user
has_many :additions
has_many :extras, :through => :additions
end
# app\models\extra.rb
class Extra < ActiveRecord::Base
belongs_to :extracat
has_many :additions
has_many :bookings, :through => :additions
def extra_info
"#{name}"
end
end
# This model is for the has_many through testing I tried
# app\models\addition.rb
class Addition < ActiveRecord::Base
belongs_to :booking
belongs_to :extra
end
# Relevant section of schema
create_table "additions", force: :cascade do |t|
t.integer "booking_id"
t.integer "extra_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "bookings", force: :cascade do |t|
t.datetime "start_time"
t.datetime "end_time"
t.integer "length"
t.integer "room_id"
t.integer "user_id"
t.integer "extra_id"
end
EDIT - The section within the bookings show page.
# app\views\bookings\show.html.erb
<% #booking.extras.each do |e| %>
<%= e.name %>,
<% end %>
EDIT - Adding bookings controller
class BookingsController < ApplicationController
respond_to :html, :xml, :json
before_action :find_room
def index
#bookings = Booking.where("room_id = ? AND end_time >= ?", #room.id, Time.now).order(:start_time)
respond_with #bookings
end
def new
#booking = Booking.new(room_id: #room.id)
end
def create
#booking = Booking.new(params[:booking].permit(:room_id, :start_time, :length))
#booking.room = #room
if #booking.save
redirect_to room_bookings_path(#room, method: :get)
else
render 'new'
end
end
def show
#booking = Booking.find(params[:id])
end
def destroy
#booking = Booking.find(params[:id]).destroy
if #booking.destroy
flash[:notice] = "Booking: #{#booking.start_time.strftime('%e %b %Y %H:%M%p')} to #{#booking.end_time.strftime('%e %b %Y %H:%M%p')} deleted"
redirect_to room_bookings_path(#room)
else
render 'index'
end
end
def edit
#booking = Booking.find(params[:id])
end
def update
#booking = Booking.find(params[:id])
# #booking.room = #room
if #booking.update(params[:booking].permit(:room_id, :start_time, :length))
flash[:notice] = 'Your booking was updated succesfully'
if request.xhr?
render json: {status: :success}.to_json
else
redirect_to resource_bookings_path(#room)
end
else
render 'edit'
end
end
private
def save booking
if #booking.save
flash[:notice] = 'booking added'
redirect_to room_booking_path(#room, #booking)
else
render 'new'
end
end
def find_room
if params[:room_id]
#room = Room.find_by_id(params[:room_id])
end
end
def booking_params
params.require(:booking).permit(:user_id, :extra_id)
end
end
How is it possible to associate the extras with a booking? As so far they are not being saved with the booking into the database. Is this a controller issue?
You're not permitting the parameters correctly - the name is extra_ids. In addition since the parameter is an array you need to permit it like so:
params.require(:booking).permit(:room_id, :start_time, :length, :extra_ids => [])
Personally I recommend setting action controller to raise an error when unpermitted parameters are encountered in development or tests - very easy otherwise to miss the log messages
I have 3 models with a has_many through relationship: Food (eg: Chocolate), Sub (Chocolate food substitute), Joint (joint table).
Say #food = Food.find(1); The has_many through relationship allows me to do #subs = #food.subs which return all substitutes associated with #food. This work fine, however only the Sub id is saved and not its attributes which are :name and :description as you can see it returned nil when trying to save #food.subs in my create action in my controller:
=> #<ActiveRecord::Associations::CollectionProxy [#<Sub id: 28,name:nil,description:nil,created_at:
"2015-01-07 00:40:35", updated_at: "2015-01-07 00:40:35">]>
I guess the issue lies with my create action in my food controller and perhaps something to do with my nested form as well. I spent countless hours trying to figure this out I am so desperate to find an answer. I really do not know where to look anymore.
I am new to rails so thanks a lot for your help and your time, I really appreciate it. Please if possible adapt your answer to my beginner level :-) .
Down below are samples of my controller, form and relevant information.
Here are my models:
class Food < ActiveRecord::Base
has_many :joints
has_many :subs, :through => :joints
accepts_nested_attributes_for :subs
end
class Sub < ActiveRecord::Base
has_many :joints
has_many :foods, :through => :joints
accepts_nested_attributes_for :foods
end
class Joint < ActiveRecord::Base
belongs_to :food
belongs_to :sub
end
Here is my db-schema FYI:
create_table "foods", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "joints", force: true do |t|
t.integer "food_id"
t.integer "sub_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subs", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Here is my foods_controller:
def new
#food = Food.new
#sub = Sub.new
end
def create
#food = Food.new(food_params)
#food.subs.build(params[:subs])
#food.save
respond_to do |format|
if #food.save
format.html { redirect_to #food, notice: 'Food was successfully created.' }
format.json { render :show, status: :created, location: #food }
else
format.html { render :new }
format.json { render json: #food.errors, status: :unprocessable_entity }
end
end
end
private
def food_params
params.require(:food).permit(:name, :description, subs_attributes: [:name, :description])
end
end
Here is my views/foods/_form:
<%= form_for(#food) do |f| %>
<% if #food.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#food.errors.count, "error") %> prohibited this food from being saved:</h2>
<ul>
<% #food.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div>
<%= f.fields_for(#sub) do |sub| %>
<div class="field">
<%= sub.label :name %>
<%= sub.text_field :name %>
</div>
<div class="field">
<%= sub.label :description %>
<%= sub.text_area :description %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
My routes in case it helps:
resources :foods
resources :subs
resources :joints
root "foods#index"
Thank you very much !
Antoine.
In your new action:
def new
#food = Food.new
#food.subs.build
end
and in your view:
<%= f.fields_for :subs do |sub| %>
When you're passing directly an object, this object becomes the new form_builder's object - rails have no idea it is in any way connected with original object so it will result in different field names.
When you pass a symbol, rails will first try to find if your current object defines subs_attributes method. If so it will loop over subs association and build the fields for each associated model.
Reference here.
UPDATE - answer to comment:
Firstly - #subs is not a symbol, it is an instance variable. Symbols start with a colon like :subs. When fields_for receives an argument, it checks whether it is a symbol or object. In former case it search an object associated with form builder (f.object) to find out if it defines <passed_symbol>_attributes=. That way it knows that the model accepts nested attributes for this association so it can behave accordingly (the new form builder is created for each associated object with a correct name - <symbol>_attributes).
When object is passed, rails has no way of detecting if this is in ay way connected to the current object - you could have two associations for the same type of objects, or even it might have absolutely nothing to do with the original object. In that case fields_for acts like it was a nested form_for - resulting form builder will carry the model name of the object (f.object.class.model_name.singular)