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
Related
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.
I'm creating a form_for in which one of the field fetches the drop-down list from the database. I'm interpolating the data to display the string but I want to store its id back into other database which is linked with my form.
class FlightsController < ApplicationController
def new
#flight = Flight.new
#airplane = #flight.airplane
#options = Airport.list
end
def create
#flight = Flight.new(flight_params)
if #flight.save!
flash[:success] = "Flight created successfully."
redirect_to #flight
else
flash[:danger] = "Flight not created."
redirect_to :new
end
end
private
def flight_params
params.require(:flight).permit(:name, :origin, :destination, :depart, :arrive, :fare, :airplane_id)
end
end
<%= form_for(#flight) do |f| %>
...
<div class="row">
<div class="form-group col-md-6">
<%= f.label :origin %>
<%= f.select :origin, grouped_options_for_select(#options), { include_blank: "Any", class: "form-control selectpicker", data: { "live-search": true } } %>
</div>
</div>
...
<% end %>
class Airport < ApplicationRecord
def self.list
grouped_list = {}
includes(:country).order("countries.name", :name).each do |a|
grouped_list[a.country.name] ||= [["#{a.country.iso} #{a.country.name}", a.country.iso]]
grouped_list[a.country.name] << ["#{a.iata} #{a.name} (#{a.city}, #{a.country.name})", a.id]
end
grouped_list
end
end
class Flight < ApplicationRecord
belongs_to :origin, class_name: "Airport"
belongs_to :destination, class_name: "Airport"
belongs_to :airplane
has_many :bookings, dependent: :destroy
has_many :passengers, through: :bookings
end
The following error is showing,
Airport(#69813853361360) expected, got "43" which is an instance of String(#47256130076180)
The output of Airport.list when run in a console is shown below:
=> {"India"=>[["IN India", "IN"], ["AGX Agatti Airport (Agatti, India)", 3], ["IXV Along Airport (Along, India)", 5], ["AML Aranmula International Airport (Aranmula, India)", 6], ["IXB Bagdogra International Airport (Siliguri, India)", 50]]}
Parameters: {"utf8"=>"✓", "authenticity_token"=>"+Z8+rkrJkkgaTznnwyTd/QjEoq3kR4ZmoUTp+EpM+320fNFg5rJm+Izx1zBODo/H7IIm3D+yg3ysnVUPmy7ZwQ==", "flight"=>{"name"=>"Indigo", "origin"=>"49", "destination"=>"11", "depart"=>"2019-02-21T21:30", "arrive"=>"2019-02-22T01:30", "fare"=>"2500", "airplane_id"=>"3"}, "commit"=>"Create Flight"}
I tried using to_i but it didn't work.
if you're interpolating a string with space delimiter you can try this.
'1 one'.split(' ').first.to_i
grouped_options_for_select is sending a.id as string value. Convert it to integer in your create action.
def create
#flight = Flight.new(flight_params)
#flight.origin = #flight.origin.to_i ## <== add this line
if #flight.save!
...
Convert string into integer in rails:
user_id = "123"
#user_id = user_id.to_i
puts #user_id
#user_id = 123
Convert integer into string in rails:
user_id = 456
#user_id = user_id.to_s
puts #user_id
#user_id = "456"
Convert column type string into integer in rails migration :
def change
change_column :webinars, :user_id, :integer, using: 'user_id::integer'
end
Convert column type integer into string in rails migration:
def change
change_column :webinars, :user_id, :string, using: 'user_id::string'
end
Your problem is not integer versus string, your problem is (and the error is telling you) the field is expecting an Airport object, but it's getting an airport id...
<%= f.select :origin, grouped_options_for_select(#options), { include_blank: "Any", class: "form-control selectpicker", data: { "live-search": true } } %>
You're trying to select origin which is an airport object. You really are just returning the ID of an airport object (origin_id).
Change it to
<%= f.select :origin_id, grouped_options_for_select(#options), { include_blank: "Any", class: "form-control selectpicker", data: { "live-search": true } } %><%= f.select :origin, grouped_options_for_select(#options), { include_blank: "Any", class: "form-control selectpicker", data: { "live-search": true } } %>
And don't forget to update your flight_params
def flight_params
params.require(:flight).permit(:name, :origin_id, :destination, :depart, :arrive, :fare, :airplane_id)
end
I would guess you'll have a similar problem with destination
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
I've build quite complex form which creates one prescription with many realtions. I am using this syntax in view:
- provide(:title, 'Create prescription')
%h1 Add medicines to prescription
.row
.span6.offset3
= form_for #prescription do |f|
= render 'shared/error_prescription_messages'
%p
= f.hidden_field :patient_id, :value => params[:patient_id]
= f.hidden_field :user_id, :value => current_user.id
= f.fields_for :relations do |builder|
= render 'child_form', :f => builder
%p= f.submit "Submit"
chlid_form is quite simple :
- it=f.options[:child_index].to_i
- n= it.to_s
%h2
= "Medicine ##{it+1}"
= f.hidden_field :medicine_id, :id => "my_medicine_id#{it}"
- if params[:prescription].nil? || params[:prescription][:relations_attributes][n.to_sym][:medicine_name].nil?
= f.autocomplete_field :medicine_name, autocomplete_medicine_name_relations_path, :id_element => "#my_medicine_id#{it}"
- else
= f.autocomplete_field :medicine_name, autocomplete_medicine_name_relations_path, :id_element => "#my_medicine_id#{it}", :value => params[:prescription][:relations_attributes][n.to_sym][:medicine_name]
= f.label :amount, "Amount of medicine boxes"
= f.number_field :amount, :value => 1
= f.label :daily
= f.number_field :daily, :value => 1
= f.label :period_in_days, "Duration of treatment (in days)"
= f.number_field :period_in_days, :value => 1
So as you can see I'm using f.options[:child_index] to get index of child (0,1,2...) cause I generate multiple items with this particular form. I then put it to variable it and sucessfully use it in :id_element => "#my_medicine_id#{it}" which works PERFECTLY fine (creates my_medicine_id0, my_medicine_id1 ....) Although it doesn't work in this line:
:value => params[:prescription][:relations_attributes][n.to_sym][:medicine_name]
where n is just n=it.to_s.
I though somethings wrong in controller but if I change this line to whatever
:value => params[:prescription][:relations_attributes]**[:'0']**[:medicine_name] or any other integer from 0 to 4 everything works great, but I NEED dynamic change in this one. So I got proof that it DOES work because it generates integer fine here "#my_medicine_id#{it}" but won't work in hash! And when I print the whole hash from params I get this:
{"patient_id"=>"7", "user_id"=>"1", "relations_attributes"=>{"0"=>{"medicine_id"=>"13490", "medicine_name"=>"Locacid 500 mcg/g (0,05%) (1 tuba 30 g)", "amount"=>"0", "daily"=>"1", "period_in_days"=>"1"}, "1"=>{"medicine_id"=>"", "medicine_name"=>"", "amount"=>"1", "daily"=>"1", "period_in_days"=>"1"}, "2"=>{"medicine_id"=>"", "medicine_name"=>"", "amount"=>"1", "daily"=>"1", "period_in_days"=>"1"}, "3"=>{"medicine_id"=>"", "medicine_name"=>"", "amount"=>"1", "daily"=>"1", "period_in_days"=>"1"}, "4"=>{"medicine_id"=>"", "medicine_name"=>"", "amount"=>"1", "daily"=>"1", "period_in_days"=>"1"}}}
so to get the values I need it's pretty obvious that
params[:prescription][:relations_attributes][SOME_KIND_OF_INETEGER][:medicine_name] should work, but doesn't.
Controller code:
class PrescriptionsController < ApplicationController
before_action :signed_in_user
before_action :doctor_user, only: [:new, :create]
before_action :pharmacist_user, only: [:update]
def new
#prescription =Prescription.new
5.times { #prescription.relations.build }
end
def create
#prescription = Prescription.new(new_prescription_params)
if #prescription.save
flash[:success] = "Prescription created."
redirect_to #prescription
else
5.times { #prescription.relations.build }
render 'new', :prescription => params[:prescription]
end
end
def show
#prescription = Prescription.find(params[:id])
#medicines = #prescription.medicines.paginate(page: params[:page], :per_page => 10)
end
def update
#prescription = Prescription.find(params[:id])
#patient = Patient.find(params[:patient_id])
if !prescription_expired?(#prescription)
#prescription.realized = 1
if #prescription.save
flash[:success] = "Prescription realized."
redirect_to #patient
else
redirect_to root_url
end
else
flash[:notice] = "Can't realize, prescription expired."
redirect_to #patient
end
end
private
def new_prescription_params
params.require(:prescription).
permit(:patient_id, :user_id, relations_attributes: [:medicine_id, :medicine_name, :amount, :daily, :period_in_days])
end
def doctor_user
redirect_to(root_url) unless current_user.function == "doctor"
end
def pharmacist_user
redirect_to(root_url) unless current_user.function == "pharmacist"
end
def prescription_expired?(presc)
presc.created_at < 1.month.ago
end
def signed_in_user
unless signed_in?
store_location
flash[:notice] = "Please log in."
redirect_to login_url
end
end
end
I run out of ideas so I ask you guys if anyone can help. Thanks.
There is no point in using params in your view since you already assigned those to your models. Also when you rendering your new action, those params doesn't exist as nothing has been send to the server yet. Just get rid of all the values from inputs.
Your partial should look like:
- it=f.options[:child_index].to_i
- n= it.to_s
%h2
= "Medicine ##{it+1}"
= f.hidden_field :medicine_id, :id => "my_medicine_id#{it}"
= f.autocomplete_field :medicine_name, autocomplete_medicine_name_relations_path
= f.label :amount, "Amount of medicine boxes"
= f.number_field :amount
= f.label :daily
= f.number_field :daily
= f.label :period_in_days, "Duration of treatment (in days)"
= f.number_field :period_in_days
If you want your fields to have default value, set default value inside your database.
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)