I'm really new to the ruby on rails framework and i try to build a recipe website to learn more about this framework.
I have a entity Recipe.rb which has many RecipeIngredient. A RecipeIngredient is a entity in relation with a Ingredient, a Unit and have a quantity.
Here are the files :
Recipe.rb
class Recipe < ActiveRecord::Base
extend Enumerize, FriendlyId
belongs_to :user
has_many :directions
has_many :recipe_ingredients
has_many :ingredients, through: :recipe_ingredients
accepts_nested_attributes_for :directions, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :recipe_ingredients, reject_if: :all_blank, allow_destroy: true
friendly_id :title, use: :slugged
has_attached_file :image, styles: { thumb: "270x200#", main: "570x420#" }, default_url: "/images/:style/missing.png"
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
enumerize :difficulty, in: [:easy, :medium, :hard], default: :easy
end
RecipeIngredient.rb
class RecipeIngredient < ActiveRecord::Base
belongs_to :ingredient
belongs_to :unit
belongs_to :recipe
end
Ingredient.rb
class Ingredient < ActiveRecord::Base
belongs_to :recipe_ingredient
end
Unit.rb
class Unit < ActiveRecord::Base
belongs_to :recipe_ingredient
end
As you can see, I use the gem Cocoon to handle nested fields. So, when the user wants to create a recipe, he has to provide the ingredients with the recipe.
My _form looks like this : (I have removed all html tag (the indentation is not good here)
= simple_form_for #recipe, html: {multipart: true} do |f|
- if #recipe.errors.any?
.alert.alert-danger
- #recipe.errors.full_messages.each do |msg|
li= msg
= f.input_field :title, label: false, placeholder: 'Nom de la recette', required: true
= f.input_field :prepareTime, label: false, placeholder: 'Temps de préparation (en mn)', required: true
= f.input_field :cookingTime, label: false, placeholder: 'Temps de cuisson (en mn)', required: true
= f.input_field :difficulty, label: false, required: true, placeholder: 'Difficulté'
= f.input_field :serves, label: false, placeholder: 'Nombre de personnes', required: true
= f.input_field :description, label: false, placeholder: 'Description', required: true
= f.simple_fields_for :recipe_ingredients do |recipe_ingredient|
= render 'recipe_ingredient_field', f: task
= link_to_add_association 'Ajouter un ingrédient', f, :recipe_ingredients, class: 'add'
= f.simple_fields_for :directions do |direction|
= render 'direction_field', f: task
= link_to_add_association 'Ajouter une étape', f, :directions, class: 'add'
= f.input_field :image, as: :file, label: false
= f.button :submit, value: 'Ajouter la recette', class: 'button'
_recipe_ingredient_fields.html.slim
.f-row.ingredient
.nested_fields
.large
= f.input_field :ingredient, label: false, placeholder: "Nom de l'ingrédient"
.small
= f.input_field :quantity, label: false, placeholder: 'Quantité'
.third.mb
= f.association :unit, label: false
= link_to_remove_association '-', f, class: 'remove'
And last but not least, my recipe controller
class RecipesController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
before_action :find_recipe, only: [:show]
def index
#recipes = Recipe.all
end
def new
#recipe = current_user.recipes.build
end
def create
#recipe = current_user.recipes.build(recipe_params)
if #recipe.save
redirect_to #recipe, notice: 'La recette a été ajoutée'
else
render 'new'
end
end
def show
end
private
def recipe_params
params.require(:recipe).permit(:title, :prepareTime, :cookingTime, :difficulty, :serves, :description, :image, directions_attributes: [:id, :step, :done, :_destroy], recipe_ingredients_attributes: [:id, :ingredient, :unit, :quantity, :done, :_destroy])
end
def find_recipe
#recipe = Recipe.friendly.find(params[:id])
end
end
So now, when I tr to create a new recipe, I have an error "Ingredient excepted, String given".
I know it's because my new ingredient is not created in my create method but I really don't know how to do this. If someone can help me :)
If you want to save the ingredient in recipe_ingredients as a string then I think your issue is caused by a name clash between your model and the input field so rename your input field to something else and update your strong parameters.
However...If instead you intend to build a new ingredient record when you add a recipe_ingredient record then you need to have a fields_for clause in order to build the ingredient record and have it associated to your recipe_ingredient field. Also make sure than your recipe_ingredient includes accepts_nested_attributes_for :ingredients
Related
I am using Ruby 2.6.5 and Rails 5.2.3. I have a form with double nested objects with Cocoon. The form is for Preorders. A Preorder can have many Dancers. Dancers can have many and belong to many Groups, so I set up a join table called DancerGroup.
I use Cocoon to add dancers_attributes to the Preorder form, and again to add dancer_groups_attributes inside of the dancers_attributes fields.
My problem is that when I attempt to save a Preorder with a new Dancer and new DancerGroup associations I get the following error:
Dancers dancer groups dancer can't be blank
Which clearly means it's trying to save the dancer_group object before the dancer record has an id The obvious answer is that since I'm using Rails 5, the belongs_to association is required by default, and the work around is to properly use inverse_of and accepts_nested_attributes_for methods to let Rails manage saving everything all at once. But it's not working when it comes to my join table. I even get the same error when I set optional: true on the belongs_to :dancer in the DancerGroup model.
My strong params look like this:
private
def preorder_params
params.require(:preorder).permit( :event_id,
:dancers_attributes => [
:_destroy,
:id,
:name,
:dancer_groups_attributes => [
:_destroy,
:group_id,
:id ]
]
)
end
My models look like this:
app/models/preorder.rb
class Preorder < ApplicationRecord
belongs_to :event
has_many :dancers, dependent: :destroy, inverse_of: :preorder
has_many :dancer_groups, through: :dancers, dependent: :destroy
with_options reject_if: :all_blank, allow_destroy: true do
accepts_nested_attributes_for :dancers
accepts_nested_attributes_for :dancer_groups
end
validates :dancers, :length => { minimum: 1 }
...
end
app/models/dancer.rb
class Dancer < ApplicationRecord
belongs_to :preorder, counter_cache: true, inverse_of: :dancers
has_many :dancer_groups, inverse_of: :dancer, dependent: :destroy
has_many :groups, through: dancer_groups
with_options reject_if: :all_blank, allow_destroy: true do
accepts_nested_attributes_for :dancer_groups
end
...
end
app/models/dancer_group.rb
class DancerGroup < ApplicationRecord
belongs_to :dancer, inverse_of :dancer_groups
belongs_to :group, inverse_of :dancer_groups
...
end
And here's what my form files look like
views/admin/preorders/_form.html.haml
...
%fieldset
%legend Dancers
#dancers
- #preorder.dancers.each do |dancer|
= f.fields_for :dancers, dancer do |ff|
= render "dancer_fields", f: ff, preorder: #preorder
= link_to_add_association f, :dancers,
{ "data-association-insertion-node": "#dancers",
"data-association-insertion-method": "append",
render_options: { locals: { preorder: #preorder } },
class: "btn btn-info btn-sm btn-simple float-right" } do
= icon "fas", "plus"
Add Dancer
...
views/admin/preorders/_dancer_fields.html.haml
...
.card.preorder_dancer
.card-header.row
.col
%h4.card-title Dancer Info
.col-auto
= link_to_remove_association f, { wrapper_class: "preorder_dancer",
class: "btn btn-sm btn-icon btn-simple btn-danger" } do
= icon "fas", "times"
.card-body
.row.pb-3
.col
= f.label :name do
Full Name
%span.text-danger *
= f.text-field :name, class: "form-control"
%h4.card-title Dance Groups
.row{ :id => "dancer_groups_#{f.object.id}" }
- f.object.dancer_groups.each do |dancer_group|
= f.fields_for :dancer_groups, dancer_group do |ff|
= render "dancer_group_fields, f: ff,
preorder: preorder
=link_to_add_association f, :dancer_groups,
{ "data-association-insertion-node": "#dancer_groups_#{f.object.id}",
"data-association-insertion-method": "append",
render_options: {locals: { preorder: preorder } },
class: "btn btn-info btn-sm btn-simple float-right" } do
=icon "fas", "plus"
Add Dance Group
views/admin/preorders/_dancer_group_fields.html.haml
.col-lg-6.preorder_dancer_group
.card.bg-dark
.card-header.row
.col
%h4.card-title Dance Group
.col.text-right
=link_to_remove_association f, { wrapper_class: "preorder_dancer_group",
class: "btn btn-sm btn-icon btn-simple btn-danger"} do
=icon "fas", "times"
.card-body
.row
.col
=f.label :group_id do
Group
%span.text-danger *
=f.collection_select :group_id,
preorder.event.groups,
:id,
:name,
{include_blank: "Select Group"},
{class: "form-control"}
In your _dancer_fields partial you write
%h4.card-title Dance Groups
.row{ :id => "dancer_groups_#{f.object.id}" }
- f.object.dancer_groups.each do |dancer_group|
= f.fields_for :dancer_groups, dancer_group do |ff|
= render "dancer_group_fields, f: ff,
preorder: preorder
and that should be
%h4.card-title Dance Groups
.row{ :id => "dancer_groups_#{f.object.id}" }
= f.fields_for :dancer_groups do |ff|
= render "dancer_group_fields, f: ff,
preorder: preorder
Because otherwise you are not making the dancer-group "connected" to the dancer.
I'm trying to implement a multiple select and save with simple form and nested forms.
My view shows all the branches with name and description in a card format (Bootstrap). Next to te name I would like to have a checkbox. I would like to save only the selected ones
= simple_form_for(#my_branch, html: { class: 'form-horizontal' }) do |f|
= f.error_notification
= f.input :user_id, input_html: { value: current_user.id}, as: :hidden
- #branches.each do |branch|
= f.simple_fields_for :my_branch_items do |b|
.col-md-4
.card{:style => "width: 18rem;"}
.card-header
= branch.name
= b.input :branch_id, input_html: { value: branch.id }, as: :radio_buttons
%ul.list-group.list-group-flush
= branch.description
.form-group
= f.submit 'Save', class: 'btn btn-primary'
Here are the associations
class Branch < ApplicationRecord
has_many :my_branch_items
end
class MyBranch < ApplicationRecord
belongs_to :user
has_many :my_branch_items
accepts_nested_attributes_for :my_branch_items, allow_destroy: true, reject_if: proc { |att| att['name'].blank? }
end
class MyBranchItem < ApplicationRecord
belongs_to :my_branch
belongs_to :branch
end
And the Controller
class MyBranchesController < BaseController
before_action :set_my_branch, only: [:show]
def new
#branches = Branch.all
#my_branch = MyBranch.new
end
def create
#my_branch = Quiz.new(my_branch_params)
if #my_branch.save
redirect_to my_branch_path(#my_branch), notice: 'Thanks for taking the Branch Quiz'
else
render :new
end
end
def show
end
private
def set_my_branch
#my_branch = MyBranch.find(params[:id])
end
def my_branch_params
params.require(:my_branch).permit(:name, :note, :user_id, my_branch_items_attributes: MyBranchItem.attribute_names.map(&:to_sym).push(:_destroy))
end
end
Fixed!
Form
= simple_form_for(#my_branch, html: { class: 'form-horizontal' }) do |f|
= f.error_notification
= f.input :user_id, input_html: { value: current_user.id}, as: :hidden
- #branches.each do |branch|
= f.simple_fields_for :my_branch_items do |b|
.col-md-4
.card{:style => "width: 18rem;"}
.card-header
= branch.name
= b.input_field :branch_id, checked_value: branch.id, as: :boolean, boolean_style: :inline, include_hidden: false
%ul.list-group.list-group-flush
= branch.description
.form-group
= f.submit 'Save', class: 'btn btn-primary'
Model was using a validation for blanks field that did not exist in the table.
class MyBranch < ApplicationRecord
belongs_to :user
has_many :my_branch_items, dependent: :destroy
accepts_nested_attributes_for :my_branch_items, allow_destroy: true, reject_if: proc { |att| att['branch_id'].blank? }
end
This is driving me crazy. Probably I'm overlooking something obvious, but I've read all other posts on the same topic and can't solve the problem. Thanks in advance for any help.
I have a model Klasse and a Model Klp. An instance of klasse has many klps. I would like to create klp in the same form as klasse. I've set this up using form_with and fields_for, and in klasses_controller.rb I have 3.times { #klasse.klps.build } But this iteration does not seem to work. The fields_for block is only shown once instead of 3 times.
The form in new.html was created following the rails "getting started" guide, and the fields_for block following the "form helpers" guide.
Here is my code:
app\models\klasse.rb:
class Klasse < ApplicationRecord
has_many :klps
has_many :people, :through => :klps
accepts_nested_attributes_for :klps, allow_destroy: true, :reject_if => :all_blank
validates :name, presence: true,
length: { minimum: 1 },
uniqueness: true
validates :klp_std_soll, presence: true,
length: { minimum: 1 }
def klp_std_ist
self.klps.sum("std")
end
end
app\models\person.rb
class Person < ApplicationRecord
has_many :klps
has_many :klasses, :through => :klps
validates :name, presence: true,
length: { minimum: 1 },
uniqueness: true
validates :vorname, presence: true,
length: { minimum: 1 }
def last_and_first_name
"#{name}, #{vorname}"
end
end
app\models\klp.rb
class Klp < ApplicationRecord
belongs_to :klasse
belongs_to :person
end
app\controllers\klasses_controller.rb
class KlassesController < ApplicationController
def new
#klasse = Klasse.new
3.times { #klasse.klps.build }
end
private
def klasse_params
params.require(:klasse).permit(:name, :klassentyp_id, :klp_std_soll, klps_attributes: [:id, :person_id, :std, :_destroy])
end
end
app\controllers\klps_controller.rb
class KlpsController < ApplicationController
def new
#klp = Klp.new
end
private
def klp_params
params.require(:klp).permit(:klasse_id, :person_id, :std)
end
end
app\views\klasses\new.html.erb
<%= form_with scope: :klasse, url: klasses_path, local: true do |form| %>
<ul>
<%= form.fields_for :klps do |klps_form| %>
<p>
<%= klps_form.label :person %>
<%= klps_form.collection_select :person_id, Person.all, :id, :last_and_first_name %>
<%= klps_form.label :std %>
<%= klps_form.text_field :std %>
</p>
<% end %>
</ul>
<p>
<%= form.submit %>
</p>
<% end %>
Try setting your collection of klps to an instance variable that you can then pass as an explicit "record object" argument to the fields_for method:
apps/controllers/klasses_controller.rb
def new
#klasse = Klasse.new
#klps = 3.times { #klasse.klps.build }
end
app\views\klasses\new.html.erb
<%= form.fields_for :klps, #klps do |klps_form| %>
... your form...
<% end %>
See also: How do I pass an array to fields_for in Rails?
Reference: https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
I am giving an example see
customer.rb
class Customer < ApplicationRecord
has_many :addresses, dependent: :destroy
accepts_nested_attributes_for :addresses, allow_destroy: true
end
address.rb
class Address < ApplicationRecord
belongs_to :customer
end
customers_controller.rb
class CustomersController < ApplicationController
def new
#customer = Customer.new
3.times {
#customer.addresses.build //You can use #customer.addresses.new
}
end
end
def customer_params
params.require(:customer).permit(:id, :name, addresses_attributes:[:id,:country, :state])
end
new.html.haml
= simple_form_for #customer do |f|
= f.input :name, label: false, wrapper: false, input_html: { class: "gui-input"}, placeholder: "Customer Name"
= f.fields_for :addresses do |a|
= render 'addresses/fields', a: a
view/addresses/_fields.html.haml
= a.input :country, as: :select, label:false, :wrapper => false, prompt: "Select country"
= a.input :city, label:false, :wrapper => false, input_html: {class: "gui-input", maxlength: 80, minlength: 2}, placeholder: "City"
for more info
https://www.pluralsight.com/guides/ruby-ruby-on-rails/ruby-on-rails-nested-attributes
use
<%= form.fields_for :klps_attributes do |klps_form| %>
How do I render a list of existing categories in my views?
My categories table has a :name column.
products view
= f.select :category_name, Category.all.map{|s| [s.name]}
Category.rb
class Category < ActiveRecord::Base
has_many :categoricals
validates :name, uniqueness: { case_sensitive: false }, presence: true
acts_as_tree order: "name"
end
Product.rb
class Product < ActiveRecord::Base
include ActionView::Helpers
include Categorizable
end
UPDATE:
Full product form
= simple_form_for #product, html: { multipart: true } do |f|
= f.input :title, placeholder: "Product Name", required: false, label: false
= f.input :description, placeholder: "Description", required: false, label: false
= f.select :category_name, Category.all.map{|s| s.name}
= f.input :image, label: false
= f.button :submit, class: "button"
UPDATE #2:
products controller
def update
if #product.update(product_params)
redirect_to #product, notice: "Your Product was successfully updated!"
else
render 'edit'
end
end
categorical model
class Categorical < ActiveRecord::Base
belongs_to :category
belongs_to :categorizable, polymorphic: true
validates_presence_of :category, :categorizable
end
categorizable module
module Categorizable
extend ActiveSupport::Concern
included do
has_many :categoricals, as: :categorizable
has_many :categories, through: :categoricals
end
def add_to_category(category)
self.categoricals.create(category: category)
end
def remove_from_category(category)
self.categoricals.find_by(category: category).maybe.destroy
end
module ClassMethods
end
end
I never understand why people don't use collection_select over select:
= f.collection_select :category_id, Category.all, :id, :name
It seems the real problem is due to your relations:
#app/models/category.rb
class Category < ActiveRecord::Base
has_many :products
end
#app/models/product.rb
class Product < ActiveRecord::Base
belongs_to :category #-> requires category_id column in products table
end
This will allow you to reference the :category_id as follows:
= simple_form_for #product, html: { multipart: true } do |f|
= f.input :title, placeholder: "Product Name", required: false, label: false
= f.input :description, placeholder: "Description", required: false, label: false
= f.collection_select :category_id, Category.all, :id, :name
= f.input :image, label: false
= f.button :submit, class: "button"
Try:
= f.select :category_name, Category.all.map{|s| s.name}
In order to make this work, you have to have category_name column present in your products table.
New to rails and I'm trying to reject an empty nested file_field but it just keeps going though. Here is the setup.
EDIT: It does actually save the image properly if one is included, just doesn't reject the empty ones.
Fihish Model
class Finish < ActiveRecord::Base
default_scope order('finishes.id ASC')
attr_accessible :name,
:title,
##belongs_to##
:sku_id,
##has_many##
:image_attributes
belongs_to :skus
has_one :image, as: :imageable, :dependent => :destroy
accepts_nested_attributes_for :image, :reject_if => lambda { |a| a[:asset].blank? }, :allow_destroy => true
validates_presence_of :title
before_save :create_name
private
def create_name
self.name = title.parameterize
end
end
Finishes Controller
def new
#finish = Finish.new
#finish.build_image
respond_to do |format|
format.html # new.html.erb
# format.json { render json: #finish }
end
end
Image Model
class Image < ActiveRecord::Base
attr_accessible :content, #remove if no longer necessary
:asset
belongs_to :imageable, polymorphic: true
mount_uploader :asset, ImageUploader
end
Form
= form_for #finish, :html => { :multipart => true } do |f|
- if #finish.errors.any?
#error_explanation
%h1= "#{pluralize(#finish.errors.count, "error")} prohibited this finish from being saved:"
%ul
- #finish.errors.full_messages.each do |msg|
%li= msg
%fieldset{id: "finishes"}
.field
= f.label :title
= f.text_field :title
#finish-image.images
= f.fields_for :image do |image_builder|
= render 'images/image_fields', f: image_builder
.actions
= f.submit 'Save'
image_fields partial
.field
= f.label :asset, "Image"
= f.file_field :asset
- if f.object.asset
.image-box
= image_tag f.object.asset_url(:thumb).to_s
.remove-fields
= link_to_remove_fields f