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
Related
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| %>
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
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.
I am trying to create a workout tracker. I have these nested resources
Routes.rb
resources :days do
resources :workouts
resources :meals
end
My models look fine:
Workout.rb
class Workout < ActiveRecord::Base
has_many :exercises
belongs_to :day
end
Day.rb
class Day < ActiveRecord::Base
has_many :workouts
has_many :meals
accepts_nested_attributes_for :workouts, :allow_destroy => true
accepts_nested_attributes_for :meals, :allow_destroy => true
end
The issue lies when I try to create a new workout..
../views/workouts/_form.html.haml
= form_for([#day, #workout]), html: {class: 'form-horizontal'}) do |f|
= f.input :workout, label: "What did you work out", input_html: { class: "form-control"}
= f.input :mood, label: "How do you feel", input_html: { class: "form-control"}
= f.hidden_field :day_id, value: params[:day_id], input_html: { class: "form-control"}
= f.submit
I cant seem to save the :day_id the workout is associated with the workout though using strong params to receive them.
WorkoutsController.rb
private
def workout_params
params.require(:workout).permit(:day_id, :name, :mood)
end
def find_day
#day = Day.find(params[:day_id])
end
Instead what gets saved is a nil for :day_id
Rails Console:
Workout id: 11, name: "Hey", mood: "no", day_id: nil, created_at: "2014-12-19 14:53:29", updated_at: "2014-12-19 14:53:29"
HELP?
PS - i tried to do
= form_for([#day, #workout]), as: :foo, html: {class: 'form-horizontal'}) do |f|
= f.input :workout, label: "What did you work out", input_html: { class: "form-control"}
= f.input :mood, label: "How do you feel", input_html: { class: "form-control"}
= f.hidden_field :day_id, value: params[:day_id], input_html: { class: "form-control"}
= f.submit
and then
def workout_params
params.require(:foo).permit(:day_id, :name, :mood)
end
But it just kept saying the :foo params where empty
The way your routes are set up, I am assuming that the day_id is being passed as a url params:
/days/:day_id/workouts
If that is the case, then you do not need to worry about #day in the form, nor the day_id in the workout_params
Form:
= form_for #workout, html: {class: 'form-horizontal'}) do |f|
= f.input :workout, label: "What did you work out", input_html: { class: "form-control"}
= f.input :mood, label: "How do you feel", input_html: { class: "form-control"}
= f.submit
Controller:
def create
#workout = #day.workouts.new(workout_params)
....
end
private
def workout_params
params.require(:workout).permit(:name, :mood)
end
def find_day
#day = Day.find(params[:day_id])
end
I have two models #product and #photo with #photo nested in #product. I am only allowed to use one form to create both. I am using this JQuery plugin to handle photo upload being how it gives me a nice preview.
However the plugin has certain restrictions in my create action so I cannot use the product create action to handle both creating the photo and product.
Is it possible to have one nested for_for use two different controllers?
And how would i do it ?
my form (haml)
= form_for #product,:url => products_path, :html => { id: "fileupload", multipart: true } do |f|
%p
= f.text_field :name, placeholder: "Name"
%p
= f.text_field :price, class: "auto", data: { a_sign: "$ " }, placeholder: "Price"
%p
= f.text_field :description, placeholder: "Description"
%p
= f.fields_for :photos do |fp|
=fp.file_field :image
%br
.files{"data-target" => "#modal-gallery", "data-toggle" => "modal-gallery"}
%p.button.start
= f.submit
You can use accept_nested_attributes for to save associated data with only one create action.
Eg:-
class AlbumsController < ApplicationController
def new
#album = Album.new
#album.photos.build
end
def create
#album = Albums.new(params[:album])
#album.photos.build unless #album.photos.present?
if #album.save
flash[:notice] = "Successfully created albumn"
respond_with(#album, :location => albums_path())
else
flash[:error] = #album.errors.full_messages
render :new
end
end
end
Model:-
class Album < ActiveRecord::Base
has_many :photos, dependent: :destroy
accepts_nested_attributes_for :photos, allow_destroy: true, reject_if: proc {|attr| attr['image'].blank? }
end
class Photo < ActiveRecord::Base
belongs_to :album
end
View:-
= form_for #album,:url => albums_path, :html => {multipart: true } do |f|
%p
= f.text_field :name, placeholder: "Name"
%p
= f.text_field :price, class: "auto", data: { a_sign: "$ " }, placeholder: "Price"
%p
= f.text_field :description, placeholder: "Description"
%p
= f.fields_for :photos do |photo|
= photo.file_field :image
%br
.files
%p.button.start
= f.submit
Use ajax to firstly upload photo and after successful response continue with product.