Trouble with form for nested models in Rails 4 app - ruby-on-rails

Okay, I have been banging my head against the wall for the past few hours trying to figure this out. I have a rails ecommerce app... the store lists products objects, and when you add the product to your cart (another object) it is added by creating a line_item object. When you are ready to checkout the line_items in your cart become an order. At this point, I am trying to allow the customer to provide a page_url (an outside website the product/service will be used on) [via a form_for text_field] to each line_item. To achieve this I created a new model called Page_url which belongs_to the order. Here is what I have that has been failing miserably (I keep getting Controller errors "undefined method `page_url'"):
here is the form:
<%= form_for(#order) do |f| %>
......
<% #items.each do |item| %>
<td><a> Provide the URL the <%= item.product.title %> will be added to</a></td>
<div class="field">
<%= f.fields_for #order, :page_url do |q| -%>
<td> <%= q.label :page_url %>
<%= q.text_field :page_url, size: 80 %>
</td>
<% end %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit t('.submit') %>
</div>
<% end %>
orders_controller:
class OrdersController < ApplicationController
skip_before_action :authorize, only: [:new, :create]
include CurrentCart
before_action :set_cart, only: [:new, :create]
skip_before_action :set_order, only: [:create, :update, :destroy]
......
# GET /orders/new
def new
if #cart.line_items.empty?
redirect_to store_url, notice: "Your cart is empty"
return
end
#items = #cart.line_items
#order = Order.new
#page_urls = #order.page_urls
end
.....
def order_params
params.require(:order).permit(:name, :address, :email, :pay_type,
:page_urls_attributes => [:page_url, :product_id,])
end
The Pages_url controller is the standard controller generated by Rails 4
Order.rb:
class Order < ActiveRecord::Base
has_many :line_items, dependent: :destroy
has_many :page_urls, dependent: :destroy
# ...
validates :name, :address, :email, presence: true
validates :pay_type, inclusion: PAYMENT_TYPES
accepts_nested_attributes_for :page_urls
Page_url.rb:
class PageUrl < ActiveRecord::Base
belongs_to :order
I appreciate any help, thanks

I usually use simple_form to create associations and it works great.
That way you can do <%= f.association :page_urls %>
They have great documentation, check here: https://github.com/plataformatec/simple_form under Associations.
Also another point:
In your Order model, I would specify the class.
has_many :page_urls, dependent: :destroy, :class_name => PageUrl
And to create nested forms with simple_form see:
https://github.com/plataformatec/simple_form/wiki/Nested-Models
Hope this helps!

Related

How to create a Child before associating it to its Parent in rails?

I have Categories (Parents) within which are listed Products (Children).
I want to be able to create a new Product directly from the navbar, anywhere in the app and then, during the creation, assign it to a Category.
However, I get the present error:
NoMethodError in Products#new
Showing /Users/istvanlazar/Mobily/app/views/products/new.html.erb where line #9 raised:
undefined method `products_path' for #<#<Class:0x00007febaa5aec98>:0x00007febae0f9e38>
Did you mean? product_show_path
## product_show_path is a custom controller that has nothing to do with this one,
enabling show and clean redirection from newest_products index bypassing categories nesting.
Extracted source (around line #9):
9 <%= form_for [#product] do |f| %>
10 <div class="form-styling">
11 <div>
12 <%= f.label :name %>
My App works as such:
Models
class Category < ApplicationRecord
has_many :products, inverse_of: :category
accepts_nested_attributes_for :products
validates :name, presence: true
end
class Product < ApplicationRecord
belongs_to :user, optional: true
belongs_to :category, inverse_of: :products
validates :category, presence: true
end
Routes
get 'products/new', to: 'products#new', as: 'new_product'
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :edit]
# resources :products, only: [:index, :show, :new, :edit]
end
Controllers
class ProductsController < ApplicationController
before_action :set_category, except: :new
def index
#products = Product.all
#products = policy_scope(#category.products).order(created_at: :desc)
end
def show
#product = Product.find(params[:id])
end
def new
#product = Product.new
#product.user = current_user
end
def create
#product = Product.new(product_params)
#product.user = current_user
if #product.save!
redirect_to category_product_path(#category, #product), notice: "Product has been successfully added to our database"
else
render :new
end
end
private
def set_category
#category = Category.find(params[:category_id])
end
def product_params
params.require(:product).permit(:name, :price, :description, :category_id, :user, :id)
end
end
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def show
#category = Category.find(params[:id])
end
def new
# Non-existant created in seeds.rb
end
def create
# idem
end
def edit
# idem
end
def update
# idem
end
def destroy
# idem
end
private
def category_params
params.require(:category).permit(:name, :id)
end
end
Views
# In shared/navbar.html.erb:
<nav>
<ul>
<li>Some Link</li>
<li>Another Link</li>
<li><%= link_to "Create", new_product_path %></li>
</ul>
</nav>
# In products/new.html.erb:
<%= form_for [#product] do |f| %>
<div class="form-styling">
<div>
<%= f.label :name %>
<%= f.text_field :name, required: true, placeholder: "Enter product name" %>
<%= f.label :price %>
<%= f.number_field :price, required: true %>
</div>
<div>
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div>
<%= f.label :category %>
<%= f.collection_select :category_id, Category.order(:name), :id, :name, {prompt: 'Select a Category'}, required: true %>
</div>
<div>
<%= f.submit %>
</div>
</div>
<% end %>
Do you have any idea of where it went wrong? Or is it impossible to Create a Child before assigning it to a Parent..?
Thanks in advance.
Best,
Ist
You haven't defined any route to handle your new product form's POST. You've defined the new_product path, but this arrangement is breaking Rails' conventions and you're not providing a work-around.
You could define another custom route, e.g. post 'products', to: 'products#create', as: 'create_new_product' and then set that in your form like form_for #product, url: create_new_product_path do |f|.
However, you should consider changing the structure so that product routes are not nested under categories. Think twice before breaking conventions this way.
Edit: I misread the intention, ignore this. Go with Jeremy Weather's answer.
Or is it impossible to Create a Child before assigning it to a Parent..?
With the way you have your relationships and validations set up: yes, it is. A Product requires a Category, through the validates :category, presence: true. And if you're on Rails 5+, the association will already be required by default (meaning that validates is actually redundant). Meaning if you try to create a Product without a Category, it will fail, and the Product will not be saved to the database.
With that constraint in place, here's what I recommend trying:
Remove the custom /products/new route
remove get 'products/new', to: 'products#new', as: 'new_product'
Add :new and :create routes to allow product creation nested under categories
Add :create and :new, to the :only array to the resources :products nested under resources :categories
Move the file views/products/new.html.erb to views/categories/products/new.html.erb (creating a new categories directory in views if necessary).
In that file, alter the form_for so that the form is POSTing to the nested :create route:
form_for [#category, #product] do |f|
Visit the URL /categories/[insert some Category ID here]/products/new, filling out some data, and see if that works.
Your routes.rb should look like this:
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :new, :create, :edit]
end

How to insert my association id into simple form for

I have an model that belongs to different models (game belongs to field and organiser), but when I fill the form to create the game, my creation method is not catching up the field reference
class Game < ApplicationRecord
belongs_to :organiser
belongs_to :field
end
class Organiser < ApplicationRecord
has_many :games, dependent: :destroy
end
class Field < ApplicationRecord
has_many :games, dependent: :destroy
end
Controller
class GamesController < ApplicationController
before_action :set_game, only: [:show, :edit, :update, :destroy]
def new
#organiser = Organiser.find(params[:organiser_id])
#game = Game.new
end
def create
#game = Game.new(game_params)
organiser_id = current_organiser.id
#organiser = Organiser.find(organiser_id)
#game.organiser = #organiser
#game.save
redirect_to organiser_games_path(#organiser)
end
def edit
organiser_id = current_organiser.id
#organiser = Organiser.find(params[:organiser_id])
end
def update
organiser_id = current_organiser.id
#organiser = Organiser.find(organiser_id)
#game.update(game_params)
redirect_to organiser_games_path(#organiser)
end
private
def game_params
params.require(:game).permit(:field_id, :total_players)
end
def set_game
#game = Game.find(params[:id])
end
_form parcel for new and edit view
<%= simple_form_for [#organiser, #game] do |f| %>
<div class="form-inputs">
<%= f.input :field_id, as: :select, collection: Field.all.collect(&:location) %>
<%= f.input :total_players %>
</div>
<div class="form-actions">
<%= f.button :submit, "Create", class: "btn btn-primary" %>
</div>
<% end %>
I am a beginner, and if you guys can help me with basic solutions that will help me to understand the complex bit, I will appreciate
I received no message, it looked like it went trough but it did not
collection: Field.all.collect(&:location)
Your collection has to be an array with the relation's id and the displayed value. Here, you only collect the value of location for each Field.
this code should do the trick:
<%= f.input :field_id, as: :select, collection: Field.all.map { |field| [field.id, field.location] } %>
field.id will be the value of your select option (currently, there's no value so it can't works), and field.location will be the text of your select option. :)

Association :patient not found, rails 4

I'm trying to associate a relation betwen 1 patient with a consultation, but I'm getting a error:
Association :patient not found
In the model I have:
class Patient < ActiveRecord::Base
belongs_to :user
has_many :consultums, through: :patients
end
class Consultum < ActiveRecord::Base
belongs_to :patients
belongs_to :user
has_one :recetum
end
in the controller I have:
class ConsultationController < ApplicationController
before_action :authenticate_user!
before_action :set_consultum, only: [:show, :edit, :update, :destroy]
respond_to :html
def index
#consultation = Consultum.all
respond_with(#consultation)
end
def show
respond_with(#consultum)
end
## when I try to create a new consultation, throw me the error ##
def new
#consultum = Consultum.new
# patient_id
respond_with(#consultum)
end
def edit
end
def create
#consultum = Consultum.new(consultum_params)
#consultum.save
respond_with(#consultum)
end
def update
#consultum.update(consultum_params)
respond_with(#consultum)
end
def destroy
#consultum.destroy
respond_with(#consultum)
end
# def patient_id
# patient = Patient.find(params[:id])
# # patient = #patient.id
# #consultum.patient_id = patient
# end
private
def set_consultum
#consultum = Consultum.find(params[:id])
end
def consultum_params
params.require(:consultum).permit(:Reason_Consultation, :Diagnostic, :TX, :Labs, :Observations, :patient_id)
end
end
so, as you can see I create the function patient_id, and I'm trying to retrieve the id from the 'patient' and put into patient_id in 'consultum' table, but seems not work...
if I uncomment patient_id function throw me this error:
Couldn't find Patient without an ID
I'm stuck, any idea?
Thanks in advance
EDIT
in my view I have:
consultation/new.html.erb
<div class="page-header">
<%= link_to consultation_path, class: 'btn btn-default' do %>
<span class="glyphicon glyphicon-list-alt"></span>
Back
<% end %>
<h1>New consultum</h1>
</div>
<%= render 'form' %>
_form.html.erb
<%= simple_form_for(#consultum) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :Reason_Consultation %>
<%= f.input :Diagnostic %>
<%= f.input :TX, placeholder: "optional" %>
<%= f.input :Labs, placeholder: "optional" %>
<%= f.input :Observations, placeholder: "optional" %>
<%= f.association :patient %>
</div>
<div class="form-actions">
<%= f.button :submit, "Save Consultation" %>
</div>
<% end %>
When creating associations in rails pay super close attention to the pluralization!
class Patient < ActiveRecord::Base
belongs_to :user
has_many :consultums, through: :patients
end
class Consultum < ActiveRecord::Base
belongs_to :patients
belongs_to :user
has_one :recetum
end
You will notice that your Consultum class does not have a patient relation. It has belongs_to :patients.
class Consultum < ActiveRecord::Base
belongs_to :patient
belongs_to :user
has_one :recetum
end
Also your attribute naming should follow the Ruby conventions!
Ruby uses snake_case for naming attributes and methods and will automatically treat any identifier which begins with an UPPERCASE letter as a constant!
This is not just a stylistic issue - MRI Ruby will let you reassign constants with a warning. Other versions or future versions may be less lenient.
Bad Good
.Reason_Consultation .reason_consultation
.Diagnostic .diagnostic
.TX .tx
.Lab .lab
.Observations .observations
https://github.com/bbatsov/ruby-style-guide
To call an action you can pass parameters as follow:
link_to "New Consultum", new_consultum_path(:id => here, yo have to put the paciente id)
Hope this helps!

"param is missing or the value is empty" in nested form

I'm currently trying to build a really simple nested form app in my quest to learn rails. In this app I have three models legal_form answer and question. I have my my answers.html.erb, set up as follows:
<%= form_for (#legal_form) do |f| %>
<h1>Title <%= #legal_form.title %></h1>
<p><%= #legal_form.description %></p>
<ul>
<% #legal_form.questions.each do |question| %>
<%= fields_for question.answers.build do |q| %>
<li>
<%= question.question_content %>
<%= q.text_field :answer_content %>
</li>
<% end =%>
<% end %>
</ul>
<p><%= f.submit "Submit" %></p>
<% end %>
Which currently grabs the three questions I have stored and renders text input boxes next to them; works without a problem. However, when I submit the values, I get the "param is missing or empty: legal_form".
I figure that this is most likely due to my strong params configuration in the legal_forms controller, see below.
class LegalFormsController < ApplicationController
before_action :find_legal_form, only: [:show, :edit, :update, :destroy, :answers]
def index
#legal_form = LegalForm.all.order("created_at DESC")
end
def show
end
def new
#legal_form=LegalForm.new
end
def create
#legal_form = LegalForm.new(legal_form_params)
if #legal_form.save
redirect_to #legal_form, notice: "Successfully created new legal form."
else
render 'new'
end
end
def edit
end
def update
if #legal_form.update(legal_form_params)
redirect_to #legal_form
else
render 'edit'
end
end
def destroy
#legal_form.destroy
redirect_to root_path, notice: "Successfully deleted form"
end
def answers
#questions=#legal_form.questions
#legal_form=LegalForm.find(params[:id])
end
private
def legal_form_params
params.reqire(:legal_form).permit(:title, :description, :questions_attribute => [:id, :question_number, :question_content, :_destroy, :answer_attributes => [:id, :answer_content, :question_id, :user_id]])
end
def find_legal_form
#legal_form=LegalForm.find(params[:id])
end
end
And, in case it's helpful, here are the models for each.
class Answer < ActiveRecord::Base
belongs_to :question
end
class LegalForm < ActiveRecord::Base
has_many :questions, :dependent => :destroy
has_many :answers, through: :entity_roles
accepts_nested_attributes_for :questions,
reject_if: proc { |attributes| attributes['question_content'].blank? },
allow_destroy: true
end
class Question < ActiveRecord::Base
belongs_to :legal_form
has_many :answers
accepts_nested_attributes_for :answers,
reject_if: proc { |attributes| attributes['question_content'].blank? },
allow_destroy: true
end
Also, as requested here's my routes file:
Rails.application.routes.draw do
resources :legal_forms do
member do
get 'answers'
end
end
resources :answers
root "legal_forms#index"
end
Any help to finally conquer nested forms would be greatly appreciated. I've been banging my head against it off and on for about a week now. Many thanks in advance.
Try in the controller
def legal_form_params
params.require(:legal_form).permit(...)
end
Also question.answers.build add it to the method of your controller and call the object that returns the responses to fields_for
UPDATE
To be sent through this form your results, should probably be like this
form
<%= f.fields_for :answers do |q| %>
...
<% end =%>
in the new method
def new
#legal_form=LegalForm.new
#answers = #legal_form.question.answers.build
end
def legal_form_params
params.require(:legal_form).permit! #temporarily
end
not tried it, but imagine how it works something like this

Rails has_many through form with checkboxes and extra field in the join model

I'm trying to solve a pretty common (as I thought) task.
There're three models:
class Product < ActiveRecord::Base
validates :name, presence: true
has_many :categorizations
has_many :categories, :through => :categorizations
accepts_nested_attributes_for :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :category
validates :description, presence: true # note the additional field here
end
class Category < ActiveRecord::Base
validates :name, presence: true
end
My problems begin when it comes to Product new/edit form.
When creating a product I need to check categories (via checkboxes) which it belongs to. I know it can be done by creating checkboxes with name like 'product[category_ids][]'. But I also need to enter a description for each of checked relations which will be stored in the join model (Categorization).
I saw those beautiful Railscasts on complex forms, habtm checkboxes, etc. I've been searching StackOverflow hardly. But I haven't succeeded.
I found one post which describes almost exactly the same problem as mine. And the last answer makes some sense to me (looks like it is the right way to go). But it's not actually working well (i.e. if validation fails). I want categories to be displayed always in the same order (in new/edit forms; before/after validation) and checkboxes to stay where they were if validation fails, etc.
Any thougts appreciated.
I'm new to Rails (switching from CakePHP) so please be patient and write as detailed as possible. Please point me in the right way!
Thank you. : )
Looks like I figured it out! Here's what I got:
My models:
class Product < ActiveRecord::Base
has_many :categorizations, dependent: :destroy
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categorizations, allow_destroy: true
validates :name, presence: true
def initialized_categorizations # this is the key method
[].tap do |o|
Category.all.each do |category|
if c = categorizations.find { |c| c.category_id == category.id }
o << c.tap { |c| c.enable ||= true }
else
o << Categorization.new(category: category)
end
end
end
end
end
class Category < ActiveRecord::Base
has_many :categorizations, dependent: :destroy
has_many :products, through: :categorizations
validates :name, presence: true
end
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :category
validates :description, presence: true
attr_accessor :enable # nice little thingy here
end
The form:
<%= form_for(#product) do |f| %>
...
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<%= f.fields_for :categorizations, #product.initialized_categorizations do |builder| %>
<% category = builder.object.category %>
<%= builder.hidden_field :category_id %>
<div class="field">
<%= builder.label :enable, category.name %>
<%= builder.check_box :enable %>
</div>
<div class="field">
<%= builder.label :description %><br />
<%= builder.text_field :description %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And the controller:
class ProductsController < ApplicationController
# use `before_action` instead of `before_filter` if you are using rails 5+ and above, because `before_filter` has been deprecated/removed in those versions of rails.
before_filter :process_categorizations_attrs, only: [:create, :update]
def process_categorizations_attrs
params[:product][:categorizations_attributes].values.each do |cat_attr|
cat_attr[:_destroy] = true if cat_attr[:enable] != '1'
end
end
...
# all the rest is a standard scaffolded code
end
From the first glance it works just fine. I hope it won't break somehow.. :)
Thanks all. Special thanks to Sandip Ransing for participating in the discussion. I hope it will be useful for somebody like me.
use accepts_nested_attributes_for to insert into intermediate table i.e. categorizations
view form will look like -
# make sure to build product categorizations at controller level if not already
class ProductsController < ApplicationController
before_filter :build_product, :only => [:new]
before_filter :load_product, :only => [:edit]
before_filter :build_or_load_categorization, :only => [:new, :edit]
def create
#product.attributes = params[:product]
if #product.save
flash[:success] = I18n.t('product.create.success')
redirect_to :action => :index
else
render_with_categorization(:new)
end
end
def update
#product.attributes = params[:product]
if #product.save
flash[:success] = I18n.t('product.update.success')
redirect_to :action => :index
else
render_with_categorization(:edit)
end
end
private
def build_product
#product = Product.new
end
def load_product
#product = Product.find_by_id(params[:id])
#product || invalid_url
end
def build_or_load_categorization
Category.where('id not in (?)', #product.categories).each do |c|
#product.categorizations.new(:category => c)
end
end
def render_with_categorization(template)
build_or_load_categorization
render :action => template
end
end
Inside view
= form_for #product do |f|
= f.fields_for :categorizations do |c|
%label= c.object.category.name
= c.check_box :category_id, {}, c.object.category_id, nil
%label Description
= c.text_field :description
I just did the following. It worked for me..
<%= f.label :category, "Category" %>
<%= f.select :category_ids, Category.order('name ASC').all.collect {|c| [c.name, c.id]}, {} %>

Resources