Params issue with nested attributes - ruby-on-rails

I'm having trouble with what I feel should be a simple issue. I'm trying to create a basic match score reporting app and I'm trying to create a match and the match players simultaneously.
My models:
class Match < ApplicationRecord
has_many :match_players
accepts_nested_attributes_for :match_players
end
class MatchPlayer < ApplicationRecord
belongs_to :player
belongs_to :match
end
My form:
.container
%h1 Log a completed match
= simple_form_for #match do |f|
= f.input :played_at, html5: true
= f.simple_fields_for :match_player do |mp|
= mp.input :player_id, collection: Player.all, label_method: lambda { |player| player.first_name + " " + player.last_name }
= f.submit "Submit Match Score"
My controller action and params:
def create
#match = Match.new(match_params)
if #match.save
player = MatchPlayer.create(player: current_player, match: #match)
opponent = MatchPlayer.create(player: Player.find(match_params[:match_player_attributes][:player_id], match: #match))
else
render :new
end
end
private
def match_params
params.require(:match).permit(:played_at, match_player_attributes: [:player_id])
end
Right now I'm getting a found unpermitted parameter: :match_player issue. If I change match_player_attributes to match_player in my params then I get unknown attribute 'match_player' for Match. The error is occurring on the first line of the create action (#match = Match.new(match_params))
Any help would be appreciated!
Edit following suggestions:
Controller:
def new
#match = Match.new
#match.match_players.build
end
def create
#match = Match.new(match_params)
if #match.save!
player = MatchPlayer.create(player: current_player, match: #match)
opponent = Player.find(match_params[:match_players_attributes]["0"][:player_id])
opponent_match_player = MatchPlayer.create(player: opponent, match: #match)
redirect_to(root_path, notice: "Success!")
else
render :new
end
end
private
def match_params
params.require(:match).permit(:played_at, match_players_attributes: [:player_id])
end
Form:
= simple_form_for #match do |f|
= f.input :played_at, html5: true
= f.simple_fields_for :match_players do |mp|
= mp.input :player_id, collection: Player.all, label_method: lambda { |player| player.first_name + " " + player.last_name }
= f.submit "Submit Match Score"
Now it's creating 3 match_players, one for the current_player and 2 of the opponent. What's going on?

Looks like the problem in the simple typo,
Try to change:
= f.simple_fields_for :match_player do |mp|
to
= f.simple_fields_for :match_players do |mp|
Also
def match_params
params.require(:match).permit(:played_at, match_player_attributes: [:player_id])
end
to
def match_params
params.require(:match).permit(:played_at, match_players_attributes: [:player_id])
end
Here is wiki with examples
UPD:
From wiki I shared with, you can find this point:
accepts_nested_attributes_for - an ActiveRecord class method that goes
in your model code - it lets you create and update child objects
through the associated parent. An in depth explanation is available in
the ActiveRecord documentation.
It means that you don't need to create opponent manually
opponent = Player.find(match_params[:match_players_attributes]["0"][:player_id])
opponent_match_player = MatchPlayer.create(player: opponent, match: #match)
Because when you send your params with nested attributes:
match_players_attributes: [:player_id] inside of match creation process match_player will be created automatically

I can see in your model, you have:
has_many :match_players
hence, in your controller and in your form, you must use match_players (plural not singular)
Thus, in your controller, you will have:
def match_params
params.require(:match).permit(:played_at, match_players_attributes: [:id, :player_id])
end
And, in your form:
...
= f.simple_fields_for :match_players do |mp|
...
Notice the last s of match_player in form and in controller.

Related

Duplicating form field while editing nested form

I have two classes Food and food_category_price.
The association looks something like this:
has_many :food_category_prices, dependent: :destroy
accepts_nested_attributes_for :food_category_prices
The foods controller looks something like this for new, create and edit actions.
def new
#food = current_department.foods.new
#food.food_category_prices.build
end
def edit
#food = current_department.foods.find(params[:id])
end
def create
#food = current_department.foods.create(food_params)
if #food.persisted?
flash[:notice] = "Food was successfully created."
redirect_to foods_path
else
flash[:error] = "Error while adding food"
render :new
end
end
My food parameters looks something like this: (I have whitelisted the nested attributes' id)
def food_params
params.require(:food).permit(:name, :description, :price, :image ,food_category_prices_attributes: [:id,:category_id,:extra_price],:category_ids => [])
end
The nested form for new action of food is:
= form_for #food do |f|
.field.form-group
= f.file_field :image, class: 'd-none'
......
......
// Nested form from here
- current_department.categories.where(category_kind: 2).each do |category|
= f.fields_for :food_category_prices do |g|
= g.label :category_id, category.name + "("+String(category.price)+")"
= g.hidden_field :category_id, value: category.id
= g.label :extra_price, "Extra Price"
= g.number_field :extra_price
The problem:
The code words perfeclty fine while creating new food, and nested food_categoy_prices.
The problem is that, when i am editing the form, the attributes are duplicating. The labels such as extra price and category_id are duplicating.

Ruby on Rails error when validates_uniqueness_of using collection_select

First, sorry for my bad English. I'm still learning.
I have 3 tables in my DB:
Problem
has_many :registers
has_many :solutions, through : :registers
Solution
has_many :problems
has_many :problems, through : :registers
Register
belongs_to: problem
belongs_to :solution
The system is working well. I am able to insert new data in all of the 3 tables.
In the views for the table/model Register, to select problems and solutions, I make use of collection_select, like this:
= form_for #register do |f|
.field
= f.label :problem_id
= collection_select( :register, :problem_id, #problems, :id, :name, {}, { :multiple => false })
.field
= f.label :solution_id
= collection_select( :register, :solution_id, #courses, :id, :name, {}, { :multiple => false })
.field
= f.label :entry_at
= f.datetime_select :entry_at
.actions = f.submit
The problem only appears when I try to add this validation to Register:
validates_uniqueness_of :student_id , scope: :course_id
Then I get:
> undefined method `map' for nil:NilClass
> = collection_select( :register, :problem_id, #problems, :id, :name, {}, { :multiple => false })
And I dont know why.
So, I tried to do the validation by the controller:
def create
#register = Register.new(register_params)
problem_id = #register.problem_id
solution_id = #register.solution_id
if Register.exists?(['problem_id LIKE ? AND solution_id LIKE ?', problem_id, solution_id ])
render 'new'
else
#register.save
respond_with(#register)
end
end
But the error remains.
I believe that the cause is the collection_select, but I don't know how to solve it.
Saying one more time, I am able to persist date in all the 3 DB tables. But when I try to avoid duplication, the error appears.
This is how I solve this problem:
def create
#register = register.new(register_params)
#if #register.save
# respond_with(#register)
#else
# #register = register.all
# render :new
#end
problem_id = #register.problem_id
solution_id = #register.solution_id
if register.exists?(['problem_id LIKE ? AND solution_id LIKE ?', problem_id, solution_id ])
#register = register.new
#solutions = Solution.all
#problems = Problem.all
flash[:error] = "Problem alread in the register for this solution"
render 'new'
else
#register.save
respond_with(#register)
end
end

NoMethodError undefined method `id' for nil:NilClass:

I know this kind of question is already answered multiple times but i seriously unable to figure it out what is causing a problem here, I am having trouble solving this problem. I keep getting the same error when i'm trying to create new registration ( http://localhost:3000/registrations/new?course_id=1 ) :
NoMethodError at /registrations
undefined method `id' for nil:NilClass
Here is my RegistrationsController:
class RegistrationsController < ApplicationController
before_action :set_registration, only: [:show, :edit, :update, :destroy]
def index
#registrations = Registration.all
end
def show
end
def new
#registration = Registration.new
#course = Course.new
#course = Course.find_by id: params["course_id"]
end
def create
#registration = Registration.new registration_params.merge(email: stripe_params["stripeEmail"], card_token: stripe_params["stripeToken"])
raise "Please Check Registration Errors" unless #registration.valid?
#registration.process_payment
#registration.save
redirect_to #registration, notice: 'Registration was successfully created.'
rescue Exception => e
flash[:error] = e.message
render :new
end
protect_from_forgery except: :webhook
def webhook
event = Stripe::Event.retrieve(params["id"])
case event.type
when "invoice.payment_succeeded" #renew subscription
Registration.find_by_customer_id(event.data.object.customer).renew
end
render status: :ok, json: "success"
end
private
def stripe_params
params.permit :stripeEmail, :stripeToken
end
def set_registration
#registration = Registration.find(params[:id])
end
def registration_params
params.require(:registration).permit(:course_id, :full_name, :company, :telephone, :email, :card_token)
end
end
My Registration Model:
class Registration < ActiveRecord::Base
belongs_to :course
def process_payment
customer_data = {email: email, card: card_token}.merge((course.plan.blank?)? {}: {plan: course.plan})
customer = Stripe::Customer.create customer_data
Stripe::Charge.create customer: customer.id,
amount: course.price * 100,
description: course.name,
currency: 'usd'
#Annotate Customer Id when Registration is Created
cusotmer_id = customer.id
end
def renew
update_attibute :end_date, Date.today + 1.month
end
end
Registration New.html.haml File :
%section#course-content
%section#ruby
%section.detailed-syllabus
.wrapper-inside
= form_for #registration, html: { class: "basic-grey" } do |f|
- if #registration.errors.any?
#error_explanation
%h2
= pluralize(#registration.errors.count, "error")
prohibited this registration from being saved:
%ul
- #registration.errors.full_messages.each do |message|
%li= message
.field
= f.hidden_field :course_id, value: #course.id
.field
= f.label :full_name
= f.text_field :full_name
.field
= f.label :company
= f.text_field :company
.field
= f.label :email
= f.text_field :email
.field
= f.label :telephone
= f.text_field :telephone
//‘Stripe.js’ will recognize the card data because we have marked the inputs with ‘data-stripe’ attribute as: number, cvv, exp-month and exp-year.
= javascript_include_tag "https://js.stripe.com/v2/"
:javascript
Stripe.setPublishableKey('#{Rails.application.secrets.stripe_publishable_key}');
= label_tag "Card Number", nil, required: true
.control-group
.controls
= text_field_tag :card_number, nil, class: "input-block-level", "data-stripe" => "number"
= label_tag "Card Verification", nil, required: true
.control-group
.controls
= text_field_tag :card_verification, nil, class: "input-block-level", "data-stripe" => "cvv"
= label_tag "Card Expires", nil, required: true
= select_tag :exp_month, options_for_select(Date::MONTHNAMES.compact.each_with_index.map { |name,i| ["#{i+1} - #{name}", i+1] }), include_blank: false, "data-stripe" => "exp-month", class: "span2"
= select_tag :exp_year, options_for_select((Date.today.year..(Date.today.year+10)).to_a), include_blank: false, "data-stripe" => "exp-year", class: "span1"
.actions
= f.submit "Registration Payment", class: "btn", style: "color: white;background: rgb(242, 118, 73);"
Does anyone know how to assist me in this? Greatly appreciate all the help.
Additional Can anyone please guide me through how to pass id between 2 models like this guy did between 2 models as he's creating a scaffold for one model but passing ID lets him create values for another model too without creating actions for another controller https://github.com/gotealeaf/stripe-basics.git
Edited:
GitHub Repository For This Code
https://github.com/ChiragArya/Stripe_CheckOut_Demo
From your comments, it appears the error is caused by :
#course.id being nil
The way to fix this is to ensure #course is defined properly. You need to do the following:
def new
#registration = Registration.new
#course = Course.find_by id: params["course_id"]
end
The other issue you have here is that your routes should be able to handle courses without having to append them with ?course_id=1:
#config/routes.rb
resources :registrations do
get :course_id, to: "registrations#new" #-> yoururl.com/registrations/:course_id
end
This will still give you the course_id param in the new action; just makes it more Rails.
--
Controller
You also need some structure in your code (you're aiming for fat model, thin controller). It looks like you're coming to Rails as a Ruby dev; you need to appreciate that Rails handles most of the exceptions etc for you.
Specifically, you need to look at how to remove code out of your actions:
def create
#registration = Registration.new registration_params
#registration.process_payment
if #registration.save
redirect_to #registration, notice: 'Registration was successfully created.'
else
# handle error here
end
end
private
def registration_params
params.require(:registration).permit(:course_id, :full_name, :company, :telephone, :email, :card_token).merge(email: stripe_params["stripeEmail"], card_token: stripe_params["stripeToken"])
end
-
`id' for nil:NilClass
Finally, you have to remember this error basically means the variable you're trying to invoke an action for is nil.
Ruby populates nil variables with a NilClass object, thus it's difficult to determine what the error actually is. All it means is that the variable you're trying to call a method on doesn't have the aforementioned method, as Ruby has populated it with the NilClass object.
Try changing Registration#new action to
def new
#course = Course.find(params[:course_id])
#registration = #course.registrations.new
end
add this in your def create
def create
#course = Course.find_by id: params["registration"]["course_id"]
#registration = Registration.new registration_params.merge(email: stripe_params["stripeEmail"], card_token: stripe_params["stripeToken"])
raise "Please Check Registration Errors" unless #registration.valid?
#registration.process_payment
#registration.save
redirect_to #registration, notice: 'Registration was successfully created.'
rescue Exception => e
flash[:error] = e.message
#course = Course.find_by id: params["registration"]["course_id"]
render :new
end

Rails Polymorphic - controller & views

Hi I'm trying to create an event depending on the eventable type. the eventable type is either group or shop.
Writing my code, I'm currently sure that their is a better way create my routes and controller (still a rails newbie)
Is there a way to create only one new and create method pass in the eventable type ?
Models:
class Event < ActiveRecord::Base
belongs_to :eventable, polymorphic: true
class Group < ActiveRecord::Base
has_many :events, as: :eventable
class Shop < ActiveRecord::Base
has_many :events, as: :eventable
Routes:
resources :events do
collection do
get :new_national_event
get :new_local_event
post :create_national_event
post :create_local_event
end
do
event-controller:
def index
#search = Search.new(params[:search])
#shop = find_user_shop(#search.shop_id)
#group = #shop.group
#shop_events = #shop.events
#group_events = #group.events
end
def new_national_event
#user = current_user
#event = #user.group.events.new
end
def new_local_event
#shop = find_user_shop(#search.shop_id)
#event = #shop.events.new
end
def create_national_event
user = current_user
#event = user.group.events
if #event.save!
flash.now[:notice] = "Votre événement national a bien été enregistré"
render :index
else
flash.now[:error] = "Erreur lors de l'enregistrement du événement national"
render :new
end
end
def create_local_event
user = current_user
#event = user.group.shop.events
if #event.save!
flash.now[:notice] = "Votre événement local a bien été enregistré"
render :index
else
flash.now[:error] = "Erreur lors de l'enregistrement du événement local"
render :new
end
end
views:
index.html.slim
= link_to new_national_event_events_path
= link_to new_local_event_events_path
new_national_event_events_path.html.slim
= form_for #event, :url => create_national_event_events_path, :method => :post do |f|
div class="field"
= f.text_field :title, :required => true
div class="field"
= f.text_field :threshold, :required => true
div class="form-actions"
=f.submit "Create", class: "btn blue"
If it is just the routing you are concerned with, and not the number of actions, you can use constraints to allow a single path variable send the request to one of a multiple number of actions, this can be useful in some cases where you may want to have multiple buttons to multiple actions all reading from a single form, or if you just want to simplify your routes variable naming.
use the commit_param_routing gem and in your routes file you can write something like:
resources :events do
collection do
post :save, constraints: CommitParamRouting.new(EventController::CREATENATIONAL), action: :create_national_event
post :save, constraints: CommitParamRouting.new(EventController::CREATELOCAL), action: :create_local_event
end
end
add the constants to your controller:
class EventController
CREATENATIONAL = "create national"
CREATELOCAL = "create local"
.....
end
and then all that is left is to add them to your view file submit buttons:
div class="form-actions"
.row
.col-xs-2
=f.submit EventController::CREATENATIONAL
.col-xs-2
=f.submit EventController::CREATELOCAL
sorry for if its not quite what you were looking for or unclear, my first answer!

How to handle strong parameters as a array in Nested Form? Rails 4

The context is as follows, I have entities that can have multiple roles. These roles are manageable by the user.
For example, Entity named "Lipsum" may be "Cashier and Salesperson". So, this is a relation many_to_many.
So I have my 3 models: Entity, type_entity and entity_by_type
class Entity < ActiveRecord::Base
has_many :entity_by_types
has_many :type_entities, :through => :entity_by_types
accepts_nested_attributes_for :entity_by_types
end
class EntityByType < ActiveRecord::Base
belongs_to :entity
belongs_to :type_entity
end
class TypeEntity < ActiveRecord::Base
has_many :entity_by_types
has_many :entities, :through => :entity_by_types
end
I have an ordinary CRUD for entity types.
Now, in the CRUD of entities, I have a field Select-Option Multiple. In which the user chooses has 1 or more types, the entity that is creating.
Then my Controller Entity is as follows:
class Logistics::EntitiesController < ApplicationController
def index
#type_entities = TypeEntity.all
render layout: false
# I use this for show All entities by TypeEntity in my view index
end
def show
end
def new
#type_entities = TypeEntity.all
#entity = Entity.new
render layout: false
end
def create
entity = Entity.new(entity_parameters)
if entity.save
flash[:notice] = "Succesfull!."
redirect_to :action => :index
else
flash[:error] = "Error."
redirect_to :action => :index
end
end
def edit
#entity = Entity.find(params[:id])
#type_entities = TypeEntity.all
#action = 'edit'
render layout: false
end
def update
entity = Entity.find(params[:id])
entity.update_attributes(entity_parameters)
flash[:notice] = "Succesfull."
redirect_to :action => :index
end
def destroy
#entity = Entity.destroy(params[:id])
render :json => #entity
end
private
def entity_parameters
params.require(:entity).permit(:name, :surname, entity_by_types_attributes: [:id, :entity_id, :type_entity_id])
end
end
And my partial form (for method create and Update) is:
= simple_form_for([:namespace, #entity], html: {class: 'form-horizontal' }) do |f|
= f.input :name, placeholder: "Nombre", input_html: { class: 'form-control' }, label: false
= f.input :surname, placeholder: "Apellidos", input_html: { class: 'form-control' }, label: false
%select.select2#type-entity-select{:name => "entity[entity_by_types_attributes][type_entity_id][]", :style => "width:100%;padding: 0;border: none;", :multiple => true}
- #type_entities.each do |tent|
%option{value: "#{tent.id}"}
= tent.name
But, when I click in button submit, and "type_entity_id" have 1 or more values; in my database only display a 1 record where, entity_id is OK, however type_entity_id is NULL.
Moreover only view a 1 record, when should see 1 or more records, depending on the number of types of choice in the form.
The problem here is the way of pass type_entity_id in form of array. So, How I can do that?
P.D
The following is how the params go to my controller:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ASD"1231+Dssr6mRJcXKh9xHDvuVDmVl4jnwIilRBsuE=", "entity"=>{"name"=>"Lorem", "surname"=>"Ipsum", "entity_by_types_attributes"=>{"type_entity_id"=>["1", "4"]}}}
Try this:
def entity_parameters
params.require(:entity).permit(:name, :surname, entity_by_types_attributes: [:id, :entity_id, {:type_entity_id => []}])
end
Edit:
In your form and in def entity_parameters replace type_entity_id with type_entity_ids
Thus, the parameter will refer to a set (array) not to a single object. These are the generic method syntaxes:
Model.associate_id = some integer
Model.associate_ids = an array (for a has_many relation)

Resources