I have a form in my rails app where a user just enters a number and the form is then submitted with ajax. A new game is created in my gamescontroller if all the parameters are present. I have two hidden_field_tags whose parameters pass to the controller very nicely but the most important parameter, the one gotten from user input, doesn't seem to pass to the controller.
My form:
<%= form_for @game, :url => {:controller => "games", :action => "create" }, :html => { :role => 'form' }, remote: true, method: :post do |f| %>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="input-group">
<%= f.text_field :user_stake, class: 'form-control' %>
<span class="input-group-btn">
<%= f.submit 'Go', :html => { :type => "button" }, class: "btn btn-default" %>
</span>
</div>
</div>
</div>
<%= hidden_field_tag 'user_id', current_user.id %>
<%= hidden_field_tag 'jackpot_id', @jackpot.id %>
<% end %>
Controller:
before_action :game_params, only: [:create]
def create
@game = Game.new(game_params)
if @game.save
@jackpot = Jackpot.find(params[:jackpot_id])
ActionCable.server.broadcast 'jackpot_channel',
users: Jackpot.where(id: @jackpot.id),
pot: @jackpot,
user: User.find(@game.user_id),
game: @game,
stake: @game.user_stake.to_s
else
ActionCable.server.broadcast 'jackpot_channel',
error: @game.errors.full_messages
end
end
def new
@game = Game.new
end
private
def game_params
params.permit(:user_stake, :user_id, :jackpot_id)
end
No matter what is typed in the @game gets saved with a user_stake of 0.0, which I have set as the default in my migrations. I have no idea what I'm doing wrong here. Any ideas? Thanks!
You're not nesting the inputs properly:
<%= form_for @game, html: { role: 'form' }, remote: true do |f| %>
<%= f.text_field :user_stake, class: 'form-control' %>
<%= hidden_field_tag 'user_id', current_user.id %>
<%= hidden_field_tag 'jackpot_id', @jackpot.id %>
# ..
<% end %>
This would give the following params hash:
{
game: {
user_stake: 1.2
},
user_id: 3,
jackpot_id: 4
}
If you send that through the whitelist you get:
{
user_id: 3,
jackpot_id: 4
}
One solution is to simply nest the inputs:
<%= form_for @game, html: { role: 'form' }, remote: true do |f| %>
<%= f.text_field :user_stake, class: 'form-control' %>
<%= f.hidden_field_tag 'user_id', current_user.id %>
<%= f.hidden_field_tag 'jackpot_id', @jackpot.id %>
# ..
<% end %>
And whitelist them properly:
private
def game_params
params.require(:game)
.permit(:user_stake, :user_id, :jackpot_id)
end
But there is a huge warning flag here - NEVER pass the current user id through the params as it makes it really easy for a malicous user to hack with nothing but the web inspector. Instead use the value directly from the session.
You can't fake that except with the users password or by cracking the session encryption.
Also if a game belongs to a jackpot I would set it up as a nested resource and put the id in the path as this creates a RESTful structure that clearly shows that you are adding children to a parent resource - instead of hiding that vital information in the request body.
# routes.rb
resources :jackpots do
resources :games, shallow: true
end
class GamesController
before_action :set_jackpot, only: [:new, :create, :index]
# GET /jackpots/:jackpot_id/games/new
def new
@game = @jackpot.games.new
end
# POST /jackpots/:jackpot_id/games
def create
@game = @jackpot.games.new(game_params) do |g|
g.user = current_user
end
if @game.save
# ...
else
# ...
end
end
# GET /jackpots/:jackpot_id/games
def index
@games = @jackpot.games
end
private
def set_jackpot
@jackpot = Jackpot.includes(:games)
.find(params[:jackpot_id])
end
def game_params
params.require(:game).permit(:user_stake)
end
end
<%= form_for [@jackpot, @game], remote: true, html: { role: 'Form' } do |f| %>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="input-group">
<%= f.number_field :user_stake, class: 'form-control' %>
<span class="input-group-btn">
<%= f.submit 'Go', :html => { :type => "button" }, class: "btn btn-default" %>
</span>
</div>
</div>
</div>
<% end %>
Note how there are no hidden inputs required.