Creating form_for for model association - ruby-on-rails

I have 2 models:
brand
has_many :products
product
belongs_to :brand
In my view (show_brand.html.erb), i displaying all information about brand using #brand.name ....
I want to create form for product that belongs_to brand i'm displaying information about.
Something like:
form_for(#brand.products) ...
How can i preform that?
How can i attach user_id to product form (product belongs_to user) without adding it in controller manually
NOTICE:
About first item in my list, i know that it can be done by upgrading routes to nested and passing array with main object and association object. But if there is another way of doing that? Without modifying routes.rb and ...

You can use accepts_nested_attributes_for.
#brand_controller.rb
def new
#brand = Brand.new
#product = #brand.products.build
end
def create
#brand = Brand.new(brand_params)
if #brand.save
.....
else
.....
end
end
private
def brand_params
params.require(:brand).permit(:id, brand_attribute_1, brand_attribute_2, products_attributes: [:id, :product_attribute_1, :user_id, :product_attribute_2])
end
In your form
<%= form_for #brand do |f| %>
----code for brand attributes ---
<%= f.fields_for #product do |p| %>
----code for product attributes----
<%= p.hidden_field user_id, :value => current_user.id %> #to attach user_id to product
<% end %>
<%= f.submit "Submit" %>
<% end %>

For question 1, you can use "nested form".
Please check below link.
http://railscasts.com/episodes/196-nested-model-form-part-1?view=asciicast
For question 2, even though you set user_id in "product form", you still have to do some check in your controller/model in case any undesired value is set to user_id. So the better way is you set it yourself at backend.

Related

Rails cant assign attributes for new entity

Can someone tell me why entity always creates with name == nil :
in ProductsController:
def create
#product = Product.new(name: params[:product][:name])
byebug
if #product.save
redirect_to users_path
end
end
in view :
<%= form_for Product.new do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
model:
class Product < ApplicationRecord
has_many :categories
attr_accessor :name
end
Can someone tell me why entity always creates with name == nil
Because of your attr_accessor. It overwrites the auto-created methods from active record (the ones that know about persistence). Just remove it.
(assuming that your table products has column name. If it doesn't, create a migration to add it.)
You'll need to ensure you're using strong params for this, otherwise the params won't pass in as expected i.e.
def create
#product = Product.new(product_params)
byebug
if #product.save
redirect_to users_path
end
end
private
def product_params
params.require(:product).permit(:name)
end
More info can be found here: http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters
Name is not saved in database because you have used name as attr_accessor.
Create name column in product table. If you already have name column then remove attr_accessor :name from product.rb model.

Rails, populate database with associated models on one form

I have looked at various answers to similar questions and haven't quite cracked it.
A wine model is defined with has_one :register, :dependent => :destroy and rightly or wrongly I have added accepts_nested_attributes_for :register. A register is defined with belongs_to :wine.
The code within wines_controller.rb for create is:
def new
#wine = Wine.new
#register = Register.new
def create
#wine = Wine.new(wine_params)
#register = #wine.registers.build(register_params)
respond_to do |format|
if #wine.save
#success
else
format.json { render json: #wine.errors, status: :unprocessable_entity }
format.json { render json: #register.errors, status: :unprocessable_entity }
end
end
end
My form for creating a new wine has the following code:
<%= simple_form_for #wine do |f| %>
# various working elements
<div class="field">
<% f.fields_for :register do |r| %>
<%= r.label :short_name %>
<%= r.text_field :short_name %>
<%= r.label :barcode %>
<%= r.text_field :barcode %>
<% end %>
</div>
When this form is called up no fields are created from the f.fields_for command but this block is executed because I can add test buttons within it to prove it is accessed.
If I try to create a wine I get the following error message:
undefined method `registers' for #<Wine:0x007f1204375330> Did you mean? register register= register_id
I believe that using .build is there to ensure data integrity: I don't want to create a wine that does not have a corresponding register. I have tried thinking about it nested attributes but that seems to be considered a bad plan by many. This current approach feels correct but I think I am missing some understanding of syntax at the very least.
At a later date it will be necessary to have other models linked to register that will not be associated to wines. I was considering a similar approach but I am happy to be told to rethink!
If I understand you correctly you have 2 issues:
Firstly fields for register aren't being displayed - this is partly because #wine.register is nil.
You should change your new action to:
def new
#wine = Wine.new
#wine.register = Register.new
In addition because you are using simple_form_for you will need to use simple_fields_for instead of fields_for
Your second issue that results in the exception tells you everything... you are trying to access #wine.registers, and not #wine.register
Change in your create method to:
#register = #wine.register.build(register_params)
This will fix that issue ... however ... all you really need to do is build the #wine object from your params - your params should be configured to permit the right nested attributes - if it is set up correctly the register object will also be built when building the #wine object.
Your model is already set to accept_nested_attributes and thus will also validate and save the register object when calling #wine.save - no need to explicitly save the register object.
You should have something like:
def wine_params
params.require(:wine).permit(
:attribute1, :attribute2,
register_attributes: [:id, :short_name, :barcode])
end
Try this
Wine and Register models
class Wine < ApplicationRecord
has_one :register, inverse_of: :wine, :dependent => :destroy
accepts_nested_attributes_for :register
end
class Register < ApplicationRecord
belongs_to :wine, inverse_of: :register
validates_presence_of :wine
end
Wines Controller
class WinesController < ApplicationController
def new
#wine = Wine.new
#wine.build_register
end
def create
#wine = Wine.new(wine_params)
if #wine.save
redirect_to #wine
else
render :new
end
end
private
def wine_params
params.require(:wine).permit(:name, register_attributes: [:simple_name])
end
end
My wine_params are specific for
rails g model wine name:string
rails g model register name:string wine_id:integer
Lastly wine form should look like this
<%= form_for #wine do |f|%>
<p>
<%= f.label :name%>
<%= f.text_field :name%>
</p>
<%= f.fields_for :register do |r|%>
<p>
<%= r.label :simple_name%>
<%= r.text_field :simple_name%>
</p>
<% end %>
<%= f.submit %>
<% end %>
So you can modify wine_params and form partial for your application specifics

How do I save and update attributes in a join table in Rails 4 HMT association?

I have a has_many through join table setup for a recipe app where Ingredient and Meal connect through MealIngredient. Within MealIngredient, I have meal_id, ingredient_id, and amount.
My question is: How can I save and update the amount column in the meal form?
My form field for adding an ingredient looks like this:
<% Ingredient.all.each do |ingredient| %>
<label>
<%= check_box_tag "meal[ingredient_ids][]", ingredient.id, f.object.ingredients.include?(ingredient) %>
<%= ingredient.name %>
</label>
<br />
<% end %>
How do I save the amount for each ingredient?
I am referencing this question found here: Rails 4 Accessing Join Table Attributes
I made a demo for you: http://meals-test2.herokuapp.com/new
--
If you're using a form, you need to use fields_for and edit it that way:
#app/controllers/meals_controller.rb
class MealsController < ApplicationController
def edit
#meal = Meal.find params[:id]
end
private
def meal_params
params.require(:meal).permit(meal_ingredient_attributes: [:amount])
end
end
#app/views/meals/edit.html.erb
<%= form_for #meal do |f| %>
<%= fields_for :meal_ingredients do |i| %>
<%= f.object.ingredient.name #-> meal_ingredient belongs_to ingredient %>
<%= i.number_field :amount %>
<% end %>
<%= f.submit %>
<% end %>
The above will output a list of ingredients for the meal and allow you to input the "amount" value.
As for checkboxes, I'd have to make a demo app to see if I can get that working. I can do this if you feel it necessary.
Another way is with has_and_belongs_to_many:
#app/models/meal.rb
class Meal < ActiveRecord::Base
has_and_belongs_to_many :ingredients do
def amount #-> #meal.ingredients.first.amount
...........
end
end
end
#app/models/ingredient.rb
class Ingredient < ActiveRecord::Base
has_and_belongs_to_many :meals
end
This way, you'll be able to add as many meals / ingredients as required, allowing you to find the "amount" with #meal.ingredients.where(ingredients: {id: "x" }).size. You could also make a method to simplify it (above).
You wouldn't need to use fields_for for this:
#app/controllers/meals_controller.rb
class MealsController < ApplicationController
def new
#meal = Meal.new
end
def edit
#meal = Meal.find params[:id]
end
def update
#meal = Meal.find params[:id]
#meal.save
end
def create
#meal = Meal.new meal_params
#meal.save
end
private
def meal_params
params.require(:meal).permit(ingredient_ids: [])
end
end
Because the HABTM record uses the has_many association in your model, it provides you with the collection_singular_ids method. This allows you to override the associated data without fields_for:
#app/views/meals/new.html.erb
<%= form_for #meal do |f| %>
<%= f.collection_check_boxes :ingredient_ids, Ingredient.all, :id, :name %>
<%= f.submit %>
<% end %>
If you wanted to add extra ingredients, you'd need to create JS to duplicate the checkbox element. This will then allow you to submit multiple ids to the controller, which will just insert them blindly into the db.
This method overrides the ingredients list, and only works if you don't have any uniqueness constraints on the habtm association / table.

Best practice in creating belongs_to object

Let's say we have the following situation:
class User < ActiveRecord::Base
has_many :tickets
end
class Ticket < ActiveRecord::Base
belongs_to :user
end
For simplicity let's say Ticket has only some text field description and integer user_id. If we open User's views/users/show.html.erb view and inside User controller we have this code which finds correct user which is selected:
def show
#user = User.find(params[:id])
end`
Now inside that show.html.erb view we also have small code snipped which creates user's ticket. Would this be a good practice in creating it?
views/users/show.html.erb
<%= simple_form_for Ticket.new do |f| %>
<%= f.hidden_field :user_id, :value => #user.id %>
<%= f.text_area :description %>
<%= f.submit "Add" %>
<% end %>
controller/tickets_controller.rb
def create
#ticket = Ticket.new(ticket_params)
#user = User.find(ticket_params[:user_id])
#ticket.save
end
def ticket_params
params.require(:ticket).permit(:user_id, :description)
end
So, when we create a ticket for user, ticket's description and his user_id (hidden field inside view) are passed to tickets_controller.rb where new Ticket is created.
Is this a good practice in creating a new object which belongs to some other object? I am still learning so I would like to make this clear :) Thank you.
You should be able to do something like this in your form:
<%= f.association :user, :as => :hidden, :value => #user.id %>
This will pass user_id through your controller to your model and automatically make an association. You no longer need the #user= line in your controller.
Don't forget that the user could modify the form on their end and send any id they want. :)
See https://github.com/plataformatec/simple_form#associations for more info.
How about getting the user from the controller using current_user so that you protect yourself from anyone that would manipulate the value of the user_id in the form. Also I think this way is much cleaner
views/users/show.html.erb
<%= simple_form_for Ticket.new do |f| %>
<%= f.text_area :description %>
<%= f.submit "Add" %>
<% end %>
controller/tickets_controller.rb
def create
#ticket = Ticket.new(ticket_params)
#ticket.user = current_user
#ticket.save
end
def ticket_params
params.require(:ticket).permit(:user_id, :description)
end

Field for in rails 3 not showing displaying elements inside

I have a model named Order and another model named Member and when I try to display fields from the Members model in my Orders view it doesn't even show when using the fields_for tag. Heres what my code looks like.
order model
class Order < ActiveRecord::Base
has_many :members
end
member model
class Member < ActiveRecord::Base
belongs_to :order
end
orders controller
class OrdersController < ApplicationController
def new
#order = Order.new
3.times { #order.members.build }
#title = "Order Form"
end
def create
#order = Order.new params[:order]
if #order.save
flash[:notice] = "Your order has been created"
redirect_to orders_path
else
#title = "Order Form"
render 'new'
end
end
end
The issue is in my orders view:
<% for member in #order.members %>
This displays 3 times but the information below doesn't
<% fields_for "...", member do |member_form| %>
<p>
Name: <%= member_form.text_field :name %>
</p>
<% end %>
<% end %>
For some odd reason the information in the fields for tag won't even display once. Am I missing something?
If you find out what I am doing wrong, can you please explain it to me because I am new to rails.
Thanks in advance!
The block given to a fields_for call on a collection will be repeated for each instance in the collection, essentially creating its own loop, so you don't really need to write your own explicit loop "for member in #order.members". Further, you can leverage nested_attributes functionality to enable saving of associated members directly with #order.save:
class Order < ActiveRecord::Base
has_many :members
accepts_nested_attributes_for :members
end
class Member < ActiveRecord::Base
belongs_to :order
end
In the view:
<%= form_for #order do |order_form| %>
...
<%= order_form.fields_for :members do |member_form| %>
Name: <%= member_form.text_field :name %>
<% end %>
<% end %>
And I think your controller create method should work as you have it.
See the API docs for fields_for, especially the One-to-many subsection.
I think you just need to get rid of the "...", in your call to fields_for, as fields for is expecting an object.
Try:
<% fields_for member do |member_form| %>

Resources