I have a form that lets me create new blog posts and I'd like to be able to create new categories from the same form.
I have a habtm relationship between posts and categories, which is why I'm having trouble with this.
I have the following 2 models:
class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
attr_accessible :title, :body, :category_ids
accepts_nested_attributes_for :categories # should this be singular?
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
attr_accessible :name
end
My form lets me pick from a bunch of existing categories or create a brand new one. My form is as follows.
# using simple_form gem
.inputs
= f.input :title
= f.input :body
# the line below lets me choose from existing categories
= f.association :categories, :label => 'Filed Under'
# I was hoping that the code below would let me create new categories
= f.fields_for :category do |builder|
= builder.label :content, "Name"
= builder.text_field :content
When I submit my form, it gets processed but the new category is not created. My command prompt output tells me:
WARNING: Can't mass-assign protected attributes: category
But, if I add attr_accessible :category, I get a big fat crash with error message "unknown attribute: category".
If I change the fields_for target to :categories (instead of category) then my form doesn't even display.
I've spent a while trying to figure this out, and watched the recent railscasts on nested_models and simple_form but couldn't get my problem fixed.
Would this be easier if I was using a has_many :through relationship (with a join model) instead of a habtm?
Thanks to everyone who answered. After much trial and error, I managed to come up with a fix.
First of all, I switched from a HABTM to a has_many :through relationship, calling my join model categorization.rb (instead of categorizations_posts.rb) - NB: the fix detailed below will likely work with a HABTM too:
Step 1: I changed my models to look like this:
# post.rb
class Post < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
attr_accessible :title, :body, :category_ids
accepts_nested_attributes_for :categories
end
#category.rb
class Category < ActiveRecord::Base
has_many :categorizations
has_many :posts, :through => :categorizations
attr_accessible :name, :post_ids
end
#categorization.rb
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
end
From the post model above: obviously, the accessor named :category_ids must be present if you want to enable selecting multiple existing categories, but you do not need an accessor method for creating new categories... I didn't know that.
Step 2: I changed my view to look like this:
-# just showing the relevent parts
= fields_for :category do |builder|
= builder.label :name, "Name"
= builder.text_field :name
From the view code above, it's important to note the use of fields_for :category as opposed to the somewhat unintuitive fields_for :categories_attributes
Step 3
Finally, I added some code to my controller:
# POST /posts
# POST /posts.xml
def create
#post = Post.new(params[:post])
#category = #post.categories.build(params[:category]) unless params[:category][:name].blank?
# stuff removed
end
def update
#post = Post.find(params[:id])
#category = #post.categories.build(params[:category]) unless params[:category][:name].blank?
# stuff removed
end
Now, when I create a new post, I can simultaneously choose multiple existing categories from the select menu and create a brand new category at the same time - it's not a case of one-or-the-other
There is one tiny bug which only occurs when editing and updating existing posts; in this case it won't let me simultaneously create a new category and select multiple existing categories - if I try to do both at the same time, then only the existing categories are associated with the post, and the brand-new one is rejected (with no error message). But I can get round this by editing the post twice, once to create the new category (which automagically associates it with the post) and then a second time to select some additional existing categories from the menu - like I said this is not a big deal because it all works really well otherwise and my users can adapt to these limits
Anyway, I hope this helps someone.
Amen.
In your form you probably should render the fields_for once per category (you can have multiple categories per post, hence the habtm relation). Try something like:
- for category in #post.categories
= fields_for "post[categories_attributes][#{category.new_record? ? category.object_id : category.id}]", category do |builder|
= builder.hidden_field :id unless category.new_record?
= builder.label :content, "Name"
= builder.text_field :content
I have made my application and my nested form works with HABTM.
My model is :
class UserProfile < ActiveRecord::Base
attr_accessible :name, :profession
has_and_belongs_to_many :cities
belongs_to :user
attr_accessible :city_ids, :cities
def self.check_city(user,city)
user.cities.find_by_id(city.id).present?
end
end
class City < ActiveRecord::Base
attr_accessible :city_name
has_and_belongs_to_many :user_profiles
end
In my form I have:
-# just showing the relevent parts
= f.fields_for :cities do|city|
= city.text_field :city_name
And at my controller:
def create
params[:user_profile][:city_ids] ||= []
if params[:user_profile][:cities][:city_name].present?
#city= City.create(:city_name=>params[:user_profile][:cities][:city_name])
#city.save
params[:user_profile][:city_ids] << #city.id
end
#user=current_user
params[:user_profile].delete(:cities)
#user_profile = #user.build_user_profile(params[:user_profile])
respond_to do |format|
if #user_profile.save
format.html { redirect_to #user_profile, notice: 'User profile was successfully created.' }
format.json { render json: #user_profile, status: :created, location: #user_profile }
else
format.html { render action: "new" }
format.json { render json: #user_profile.errors, status: :unprocessable_entity }
end
end
end
def update
params[:user_profile][:city_ids] ||= []
if params[:user_profile][:cities][:city_name].present?
#city= City.create(:city_name=>params[:user_profile][:cities][:city_name])
#city.save
params[:user_profile][:city_ids] << #city.id
end
#user=current_user
params[:user_profile].delete(:cities)
#user_profile = #user.user_profile
respond_to do |format|
if #user_profile.update_attributes(params[:user_profile])
format.html { redirect_to #user_profile, notice: 'User profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #user_profile.errors, status: :unprocessable_entity }
end
end
end
This code works.
Maybe you should try it with (not testet):
attr_accessible :category_attributes
And HBTM relations arent really recommened... But I use them on my own :P
Related
I have a 3 models. User, CV, and Language. A User has one CV. A CV has many Languages. The User has many Languages through its CV. When I try to save the form I get an error that the Language does not have a User ID. How can I get the User ID to pass through the CV and to the Language in my form?
The CV is receiving the User ID properly. Languages is not.
I am using the Simple-Form and Cocoon gems.
Simplified version of form
= simple_form_for(#cv, url: user_cvs_path) do |f|
= f.simple_fields_for :languages do |language|
From User Model
has_one :cv, dependent: :destroy
has_many :languages, through: :cv, inverse_of: :user
From Cv Model
belongs_to :user
has_many :languages, dependent: :destroy
accepts_nested_attributes_for :languages, allow_destroy: true
From Language Model
belongs_to :user
belongs_to :cv
From the CV Controller
before_action :set_user
def new
#cv = #user.build_cv
#cv.languages.build
end
def create
#cv = #user.create_cv(cv_params)
respond_to do |format|
if #cv.save
format.html { redirect_to user_cv_url(#user, #cv), notice: 'Cv was successfully created.' }
format.json { render :show, status: :created, location: #cv }
else
format.html { render :new }
format.json { render json: #cv.errors, status: :unprocessable_entity }
end
end
end
def cv_params
params.require(:cv).permit(
:user_id,
:first_name,
:middle_name,
:last_name,
... # lots of params left out for brevity
languages_attributes: [
:id,
:cv_id,
:user_id,
:name,
:read,
:write,
:speak,
:listen,
:_destroy])
end
def set_user
#user = current_user if user_signed_in?
end
Your Language model does not need the belongs_to :user. Language belongs to CV and CV belongs to User, so the relation between Language and User is already in place. If you need to access the user for a specific language you can write #language.cv.user
To solve your problem just remove the belongs_to :user from the Language model, remove the user_id from languages_attributes, and remove the user_id from languages table.
i use rails 5 , simple form. in my app there is a Category model and there is a OnlineProduct model. i dont know why when i want to add some categories to my OnlineProduct association table remain empty and don't change.
Category model:
class Category < ApplicationRecord
has_ancestry
has_and_belongs_to_many :internet_products
end
InternetProduct model:
class InternetProduct < ApplicationRecord
belongs_to :user
belongs_to :business
has_and_belongs_to_many :categories
end
InternetProduct controller:
def new
#internet_product = InternetProduct.new
end
def create
#internet_product = InternetProduct.new(internet_product_params)
respond_to do |format|
if #internet_product.save
format.html { redirect_to #internet_product, notice: 'Internet product was successfully created.' }
format.json { render :show, status: :created, location: #internet_product }
else
format.html { render :new }
format.json { render json: #internet_product.errors, status: :unprocessable_entity }
end
end
end
private:
def internet_product_params
params.require(:internet_product).permit(:name, :description, :mainpic, :terms_of_use,
:real_price, :price_discount, :percent_discount,
:start_date, :expire_date, :couponŲlimitation, :slung,
:title, :meta_data, :meta_keyword, :enability, :status,
:like, :free_delivery, :garanty, :waranty, :money_back,
:user_id, :business_id,
categoriesŲattributes: [:id, :title])
end
and in the view only the part of who relate to categories :
<%= f.association :categories %>
all the categories list in view (form) but when i select some of them not save in database. in rails console i do this
p = InternetProduct.find(5)
p.categories = Category.find(1,2,3)
this save to database without any problem, what should i do ?
tanks for reading this
I found solution to solve this. when we use has_and_belong_to_many or any other relation , if you want to use collection select in simple_form , in the model also should be add this command for nesting form
accepts_nested_attributes_for :categories
also in the controller in related method for example in the new we should
def new
#internet_product = InternetProduct.new
#internet_product.categories.build
end
I have three models: User, Question, and Answer, as follows:
Class Answer < ActiveRecord::Base
belongs_to :user
belongs_to :question
end
Class Question < ActiveRecord::Base
belongs_to :user
has_many :answers
end
Class User < ActiveRecord::Base
has_many :questions
has_many :answers
end
My main business logic lay on the idea that a user post a question, and other users answer it. I want to be able to track a question answers as well a user answers, something like:
#user.answers and #question.answers.
The view contain the question content and the answer's form.
I'm able to track the user via devise current_user helper.
How the answers create action should look like? It's a bit confusing for me, as for a single association I would just use build.
question.rb #enable nested_attributes
accepts_nested_attributes_for :answers, :reject_if => proc { |o| o['content'].blank? } #assuming Answer has `content` field to hold the answer. Or replace with exact one.
routes.rb
resources :questions do
member do
post :answer
end
end
Questions controller
def answer
#question = Question.find(params[:id])
#answer = #question.answers.build(params[:answer])
#answer.user_id = current_user.id
respond_to do |format|
if #answer.save
format.html { redirect_to #question, notice: 'Question was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "show" } #assuming your comment form is here.
format.json { render json: #answer.errors, status: :unprocessable_entity }
end
end
end
Your answer form might look like this:
<%= form_for(:answer, url: answer_question_path(#question)) do |f| %>
<%= f.text_field :content, :placeholder => "Your Answer" %> #You may modify your answer fields here.
<%= f.submit 'Answer' %>
<% end %>
I have to models:
class Patient < ActiveRecord::Base
attr_accessible :bis_gultigkeit, :geburtsdatum, :krankenkassennummer, :kvbereich, :landercode, :name, :namenszusatz, :plz, :statuserganzung, :strasse, :titel, :versichertennumer, :versichertenstatus, :vorname, :wohnort, :geschlecht, :telefon, :email, :gewicht
has_many :diagnosis
end
class Diagnose < ActiveRecord::Base
attr_accessible :beschreibung, :code, :seite, :sicherheit, :typ, :patient_id
belongs_to :patient
end
How you can see the two models have an association.
So that i want to display on the patient show page all of his diagnosis.
def show
#patient = Patient.find(params[:id])
#diagnosis = #patient.diagnosis
respond_to do |format|
format.html # show.html.erb
format.json { render json: #patient }
end
end
And in my view i call:
<%= #diagnosis.inspect %>
But somehow i get the error:
uninitialized constant Patient::Diagnosi
I cannot explain me why i get this error? And why does it say Diagnosi? I mean my model name is Diagnose! Thanks
You can call Diagnose.class_name.pluralize to see how rails pluralizes it.
I guess it is "Diagnoses", so you shoudl call:
#diagnoses = #patient.diagnoses
and
<%= #diagnoses.inspect %>
I'm stumped on my Rails app. I've created 3 models: user, event, category that have the following associations:
class User
has_many :events, :dependent => :destroy
attr_accessible :name, :email, :password, :password_confirmation, :remember_me
class Category
attr_accessible :name
has_many :events
class Event
attr_accessible :address, :cost, :date, :details, :end_time, :fav, :start_time, :title, :venue
belongs_to :user
belongs_to :category, :foreign_key => :name #not sure if this foreign key is working
The idea is that Users create events that are filed under a single category. The categories are pre-populated so users don't get to make them, only choose which to file under.
My user and events association has been working for a while, but since I've added the category model I get a "Can't mass-assign protected attributes: category" error when I try to create a new event.
I've been browsing pages all day and can't seem to track the error down. I tried adding a foreign key using belongs_to :category, :foreign_key => :name in the Event class but that didn't seem to help.
Would graciously appreciate any help, solutions, and/or pointers in the right direction!
Edit 2: I'm pretty new at Rails, but I think I've tracked down where the problem is from the error screen. Says "ActiveModel::MassAssignmentSecurity::Error in EventsController#create" and further down says "app/controllers/events_controller.rb:58:in create" which equates to this line of code: #event = current_user.events.new(params[:event]).
If I'm reading it correctly, that would mean the error occurs because I'm trying to create a new event with a category param passed in the hash and it doesn't know what to do with it. Unfortunately, I don't know what to do either...
Edit 3: As requested, here's the Event controller's create action:
def create
#event = current_user.events.new(params[:event])
respond_to do |format|
if #event.save
format.html { redirect_to #event, notice: 'Event was successfully created.' }
format.json { render json: #event, status: :created, location: #event }
else
format.html { render action: "new" }
format.json { render json: #event.errors, status: :unprocessable_entity }
end
end
end
Place the :foreign_key => :name in class User OR provide attr_accessible to class User
Add :category to attr_accessible in Event model.
And using name as foreign key is a bad idea, becouse it isn't unique
For create event with relation with user and category you can do:
user.events.build do |event|
event.category = category
end
or
category.events.build do |event|
event.user =user
end
Also, you can use method create