I had this working very similar to another controller however i needed to change this relation to another controller called agreements_controller. I want to create a has one model. review has one and belongs to agreements.
Why isn't the row being created properly?
reviews_controller:
class ReviewsController < ApplicationController
def create
#review = Reviews.create(review_params)
end
private
def review_params
params.require(:review).permit(:comment, :star, :agreement_id, :user_id, :reviser_user_id)
end
end
_form.html.erb
<%= form_for([agreement, agreement.build_review] ) do |f| %>
<% end %>
agreement.rb
class Agreement < ActiveRecord::Base
has_one :review, :dependent => :destroy
end
review.rb
class Review < ActiveRecord::Base
belongs_to :agreement
belongs_to :reviser_user
belongs_to :user
end
I've tried to find similar examples online, but all I could find was nested forms... I don't need a nested form I just want the review to create as a has one.
Models are Singular. Use
Review.create(review_params)
Related
I've implemented polymorphic comments on my Rails app using this tutorial.
So I have:
class Place < ApplicationRecord
has_many :comments, as: :commentable
end
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
Now I want to index all comments and join them with the places they comment on, but I have no idea on how to achieve this.
In my comments_controller.rb I have:
class CommentsController < ApplicationController
def index
#comments = Comment.all
end
end
And in my comments index.html.erb I can access:
<%= comment.commentable_type %>
<%= comment.commentable_id %>
But how do I access the actual attributes of what the comments are on, example: <%= comment.place.name %>?
I believe it is related to this answer but I don't know how: rails joins polymorphic association
You can refer to the commentable object with just
comment.commentable
The only thing to watch out for here is that it assumes that all your commentables have the method you call on them, such as name.
comment.commentable.name
You will want to preload these in your controller to avoid an N+1 as well.
class CommentsController < ApplicationController
def index
#comments = Comment.includes(:commentable).all
end
end
mccalljt answer's above is much better
Believe I've got it:
Added to comment.rb:
belongs_to :place, foreign_key: 'commentable_id'
Added to comments_controller.rb:
#comments = Comment.where(commentable_type: "Place").joins(:place)
Though this is not reusable when there are more associations, but I could change it to:
#place_comments = Comment.where(commentable_type: "Place").joins(:place)
#other_comments = Comment.where(commentable_type: "Other").joins(:other)
I have 3 models: Employers, Partners and Collaborations.
As an Employer, I want to add a record to my Partner model and to my Collaboration model to be able to indicate a collaboration between a Partner and a Employer. I therefore have the following columns in my database/tabels.
Models
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :partners, :through => :collaborations
end
class Partner < ActiveRecord::Base
has_many :collaborations
has_many :employers, :through => :collaborations
accepts_nested_attributes_for :collaborations
end
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
end
Tables
Collaborations
employer_id:integer
partner_id:integer
tarive:string
Partners
added_by:integer
name:string
Because I want to be able to add a Partner/Collaboration within 1 form, I use nested forms. So I can add a partner (name, etc) and a collaboration (tarive, etc) in one go.
My (simple_form) form looks like this (I have named_space resource).
Te reduce clutter, I removed as much HTML mark_up as I could, this is not the issue.
Form
/views/employer/partners/_form
= simple_form_for [:employer, #partner], html: { multipart: true } do |f|
Partner
= f.input :name, input_html: { class: 'form-control' }
= f.simple_fields_for :collaborations do |ff|
Tarive
= ff.input :tarive, input_html: { class: 'form-control' }
= f.button :submit, "Save"
My controller looks like
class Employer::PartnersController < ActionController::Base
def new
#partner = Partner.new
#partner.collaborations.build
end
def create
#partner = Partner.new(partner_params)
#partner.collaborations.build
#partner.added_by = current_employer.id
#partner.collaborations.employer_id = current_employer.employer_id
#partner.collaborations.partner_id = #partner.id
#partner.collaborations.added_by = current_employer.id
if #partner.save
redirect_to employer_partner_path(#partner), notice: "Succes!"
else
render 'new'
end
end
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:id, :employer_id, :partner_id, :tarive])
end
end
Problem
The problem/question I have is this. The attributes are assigned nicely and added in the model. But I want to add a employer_id as well, which I have in current_employer.employer.id (Devise). I do not want to work with hidden forms, just to avoid this issue.
I assigned 'parent' models always like #partner.added_by = current_employer.id and that works beautifully.
When I use:
#partner.collaborations.employer_id = current_employer.employer_id
I get an error, saying #partner.collaborations.employer_id is empty.
Question
How can I assign a variable to the nested_form (Collaboration) in my controller#create?
Or more specifically: how can I assign current_employer.employer_id to #partner.collaborations.employer_id?
There are several ways:
Merge the params
Deal with objects, not foreign keys
Personally, I feel your create method looks really inefficient. Indeed, you should know about fat model skinny controller - most of your associative logic should be kept in the model.
It could be improved using the following:
#app/controllers/employers/partners_controller.rb
class Employers::PartnersController < ApplicationController
def new
#partner = current_employer.partners.new #-> this *should* build the associated collaborations object
end
def create
#partner = current_employer.partners.new partner_params
#partner.save ? redirect_to(employer_partner_path(#partner), notice: "Succes!") : render('new')
end
private
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:tarive]) #when dealing with objects, foreign keys are set automatically
end
end
This would allow you to use:
#app/views/employers/partners/new.html.erb
= simple_form_for #partner do |f| #-> #partner is built off the current_employer object
= f.input :name
= f.simple_fields_for :collaborations do |ff|
= ff.input :tarive
= f.submit
... and the models:
#app/models/partner.rb
class Partner < ActiveRecord::Base
belongs_to :employer, foreign_key: :added_by
has_many :collaborations
has_many :employers, through: :collaborations
accepts_nested_attributes_for :collaborations
end
#app/models/collaboration.rb
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
belongs_to :creator, foreign_key: :added_by
before_create :set_creator
private
def set_creator
self.creator = self.employer_id #-> will probably need to change
end
end
#app/models/employer.rb
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :employers, through: :collaborations
end
This may not give you the ability to set tarive, however if you cut down the manual declarations in your model, we should be able to look at getting that sorted.
The main thing you need to do is slim down your code in the controller. You're being very specific, and as a consequence, you're encountering problems like that which you mentioned.
I build my relations:
# models
class Lead < ActiveRecord::Base
has_many :practices, through: :lead_practices
accepts_nested_attributes_for :practices
end
class Practice < ActiveRecord::Base
has_many :leads, through: lead_practices
end
# leads controller
def create
#lead_profile = LeadProfile.new lead_profile_params
puts "lead practice #{#lead.practices.first}"
puts " practice lead #{#lead.practices.first.lead.first}"
end
# view:
<%= form_for #lead do |f| %>
<%= f.text_field :something %>
<%= f.fields_for :practices do |practice_builder| %>
<%= practice_builder.text_field :something_else %>
<% end %>
<% end %>
The problem is in the create action, I can access the practice through lead but I cannot access the lead through practice. This becomes an issue when I want to access the lead through practice in the practice's before_create callback:
class Practice < ActiveRecord::Base
before_create :do_something
def do_something
lead.first.do_something # raises an exception because practices is nil, even though it should be populated with the relation
This seems like a common use case. How can I access the reciprocal relation?
Have you tried has_and_belongs_to_many instead of has_many :through? That is:
class Lead < ActiveRecord::Base
has_and_belongs_to_many :practices
end
class Practice < ActiveRecord::Base
has_and_belongs_to_many :leads
end
I don't know whether this will improve your situation, but it seems like it's worth a shot, as writing it this way means that ActiveRecord doesn't have to work as hard to understand the inverse relationship between the two associations, which seems key here.
I have a new form that creates an Item (all the codes are obviously simplified):
<%= simple_form_for #item do |f| %>
<%= f.input :brand_name %>
<%= f.button :submit %>
<% end %>
The current user will create an item and link it to a new or to an existing brand.
This field doesn't exist in the database; it'll be used as a way to associate all models. Hence, I create its getter and setter.
def Item < ActiveRecord::Base
belongs_to :user
belongs_to :brand
attr_accessible :brand_name
def brand_name
brand.try :name
end
def brand_name=(name)
if name.present?
brand = user.brands.find_or_initialize_by_name(name)
brand if brand.save
end
end
end
class ItemsController < ApplicationController
def new
#item = current_user.items.build
end
def create
#item = current_user.items.build(params[:item])
if #item.save
...
end
end
end
The problem is that when the form is submitted, I get this error, which lies in the product_name=() method. I've done some debugging through Rails' console and it goes all fine, but in the browser the setter method is called before the create action. That is, the record doesn't even have a user associated to it. I tried leaving the create method empty, for example, but nothing different happens.
undefined method `brands' for nil:NilClass
What is really weird is that this was working a couple of weeks ago (I've checked my git commits and the code is identical).
I though about calling the before_create callback, but there's no way to know which user should be linked.
UPDATE
I'm using Sorcery as the authentication handler. Everything always works fine, except for this create action.
class User < ActiveRecord::Base
authenticates_with_sorcery!
belongs_to :company
has_many :items
end
class Company < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :brands, dependent: :destroy
end
I have read pretty much every question here about the nested forms with has_many through associations, but I can't get my model to work. Can someone please help?
There are 2 models: archetypes and skirtpreferences, linked through a skirtpreferencing model.
Here are the models:
class Archetype < ActiveRecord::Base attr_accessible :occasion,
:skirt_partworth, :title, :skirtpreferencings_attributes
has_many :skirtpreferencings has_many :SkirtPreferences, :through
=> :skirtpreferencings accepts_nested_attributes_for :SkirtPreferences accepts_nested_attributes_for :skirtpreferencings
end
Blockquote
class Skirtpreferencing < ActiveRecord::Base attr_accessible
:archetype_id, :skirt_preference_id, :skirtpreferencing_attributes
belongs_to :archetype belongs_to :SkirtPreferences
accepts_nested_attributes_for :SkirtPreferences
end
class SkirtPreference < ActiveRecord::Base attr_accessible
:archetype_id, ....
has_many :skirtpreferencings has_many :archetypes, :through =>
:skirtpreferencings
end
The form looks like this and that is displaying just fine:
<%= form_for(#archetype) do |f| %> ...
<%= f.fields_for :skirtpreferencing do |preference_builder| %>
<%= preference_builder.fields_for :SkirtPreferences do |builder| %>
<%= render "skirt_preferences_field", :f => builder %>
<% end %> <% end %> ...
I imagine I hav to do something in the controllers, but I am not sure exactly what.
Thanks!
Adding the Controllers:
class ArchetypesController < ApplicationController
def new
#archetype = Archetype.new
#archetype.skirtpreferencings.build
end
# GET /archetypes/1/edit
def edit
#archetype = Archetype.find(params[:id])
end
def create
#archetype = Archetype.new(params[:archetype])
end
class SkirtPreferencesController < ApplicationController
def new
#skirt_preference = SkirtPreference.new
end
def edit
#skirt_preference = SkirtPreference.find(params[:id])
end
def create
#skirt_preference = SkirtPreference.new(params[:skirt_preference])
end
I am guessing this is many-to-many relationship between SkirtPreference and Archetype, while SkirtPreferencing is the association between SkirtPreference and Archetype?
Try changing from skirtpreferencing_attributes to skirtpreferences_attributes. That's my hunch. Because you are trying to add data to skritpreferences, not the skirtpreferencings which are just there to associate between skirtpreferences and archetypes.
I also think this is unusual.
has_many :SkirtPreference, :through => :skirtpreferencings
accepts_nested_attributes_for :SkirtPreference
...
belongs_to :SkirtPreference
accepts_nested_attributes_for :SkirtPreference
All these normally should be :skirtpreferences.
** EDIT 1 **
Assuming the forms are generated in new action in ArchetypesController...
You seem to be missing the part where you build a skirtpreferencing attribute out of archetype.
So in your new action in ArchetypesController
def new
#archetype = Archetype.new
#archetype.skirtpreferencings.build
...
end
** Edit 2 **
SkirtPreferences should be changed to skirtpreferences except for class name.
Can you try changing f.fields_for :skirtpreferencing do to f.fields_for :skirtpreferencings do