Rails, saving the foreign key in a `belongs_to` association - ruby-on-rails

I think I'm having a really basic problem here but I can't seem to put my finger on what I'm doing wrong.
So the issue here is when I save an instance of a model the foreign_key for the models's belongs_to association (in this case the user_id is not being saved, so I'm forced to do this:
def new
#thing = Thing.new(:user_id => current_user.id)
end
def create
#thing = Thing.new(params[:thing])
#thing.user_id = current_user.id
if #thing.save
redirect_to #thing
else
render 'new'
end
end
Shouldn't the user_id get saved automatically if my model has this association?
class Thing < ActiveRecord::Base
belongs_to :user
end
The reason I'm having this issue in the first place is because the gem friendly_id has changed the way all of my ids work and now return the objects slug... pretty annoying in my opinion.

I would try #thing.user = User.find(current_user.id) instead in your controller. Have you also got the has_many :things association declared in your user model?

Related

"ActiveModel::ForbiddenAttributesError " on Rails App ( 5) with nested attributes

I have a simple_form_for that creates an invoice. Through this form, I want the user to be able to create a client that will be associated with that before-mentionned invoice. The current process being to firstly create the client, then associate it to the invoice by selecting it from a collection of created clients when the user create an invoice.
My models :
class Client < ApplicationRecord
has_many :invoices
end
class Invoice < ApplicationRecord
belongs_to :client
accepts_nested_attributes_for :client, reject_if: :all_blank, allow_destroy: true
end
Invoice controller:
def new
#invoice = Invoice.new
#invoice.build_client
end
def create
#invoice = Invoice.new(invoice_params)
#client = #invoice.build_client(params[:invoice][:client_attributes])
#client.user = current_user
#client.save
end
And I made sure to update my strong params in Invoice Controller with :
params.require(:invoice).permit(:param1, :param2,client_attributes:[:param1, :param2, :param3, etc..],..)
That being said, when creating an invoice, I ran into an "ActiveModel :: ForbiddenAttributesError", which is set to appears when strong params are not correctly defined. Which, in my case, does not seem to be the case.
I found out that adding "params.permit!" in my #Create in the Invoice Controller, allowed me to avoid that error. But that's a trick. It should not be necessary since that's the jobs of the strong params. Has anyone ever came across a similar case, please?
Ok, so I figured this thing out. All that was needed to do was to - obviously- save my client before, my invoice. Rather simple, isn't it!
Here is my final Invoice #New #Create
def new
#invoice = Invoice.new
#invoice.build_client
end
def create
#invoice = Invoice.new(invoice_params)
#invoice.client.user = current_user
#invoice.client.save
if #invoice.save
redirect_to #invoice
else
render :new
end
end

Rails - how to show attribute of an associated model

I am trying to make an app in Rails 4.
I just asked this related question and got a clear answer. It seems I can't understand how to take that logic and apply it elsewhere.
Rails How to show attributes from a parent object
I have a user model, profile model a projects model and a universities model.
Associations are:
Profile belongs to university
Profile belongs to user
University has many profiles
University has many projects
Projects HABTM user
Projects belong to universities
In my projects controller, I define #creator as follows:
def create
logger.debug "xxx create project"
#authorise #project
#project = Project.new(project_params)
#project.creator_id = current_user.id
#project.users << current_user
respond_to do |format|
if #project.save
format.html { redirect_to #project }
format.json { render action: 'show', status: :created, location: #project }
else
format.html { render action: 'new' }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
I try to define creator_profile like this:
def show
#authorise #project
#project = Project.find(params[:id])
#creator = User.find(#project.creator_id)
#creator_profile = #creator.profile
end
In my uni table, I have attributes called logo and name. I use avatar uploader in which i have logo defined (that's why I have two .logo below).
In my projects, show, I want to display the university that the project creator belongs to.
I've tried this:
<%= image_tag(#creator_profile.university.logo.logo) %>
<div class="generaltext"><%= #creator_profile.university.name %> </div>
I get this result: undefined method `logo' for nil:NilClass
Based on the link to my problem above
<%= image_tag(creator_profile.university.logo.logo) %>
<div class="generaltext"><%= creator_profile.university.name %> </div>
I get this result:
undefined local variable or method `creator_profile' for #<#<Class:0x007f998f17ad88>:0x007f998d1ce318>
I'm not sure I understood the very detailed explanations given in the answer to my previous question. If the first version is right, then I don't understand the explanation at all. If the second version is right, then why does this error message come up?
Im wondering if the problem arises out of there not being an association between university and user? I was hoping, based on the user who created the project, to find the uni that the creator belongs to.
That's why i tried:
<%= image_tag(creator_profile.project.university.logo.logo) %>
<div class="generaltext"><%= creator_profile.project.university.name %> </div>
I get this error:
undefined method `project' for #<Profile:0x007f998ada41b8>
I think that you need to understand some basic concepts of Ruby and Ruby and Rails to solve this question yourself.
In ruby, vars with # are instance variables and are available all over the class. That means that they will be available in your view if you declare them in your controller.
EG #creator_profile = #profile.user
On the other hand, vars without # are only available inside the same block.
An example:
#controller
#users = User.all ##users, instance variable
#view
<% #users.each do |user| %>
<h3><%= user.name %></h3> #user, local variable. This will work
<% end %>
<h3><%= user.name %></h3> #this won't work because it is outside the block
Google about ruby vars and scopes.
Also, I think that you are relying too much on 'rails magic' (or you are skipping some code lines), if you don't declare an instance var, it won't exist. Naming conventions don't work that way.
At last but not at least, having a look at your relations, I think that they need some refactor. Also the use of singular and plural is not correct. I know that it's not real code but it denotes that they don't reflect real relationships between entities.
Don't try to make 'octopus' models, where everybody belongs to everybody, and think about the relationships itself, not only trying to associate models. EG:
Profile
belongs_to :creator, class_name: 'User'
This way you can write:
#controller
#profile_creator = Profile.find(params[:id]).creator
#view
#profile_creator.university
You will understand better what you are doing.
Hope it helps.
It seems I can't understand how to take that logic and apply it elsewhere.
I don't think you appreciate how ActiveRecord associations work in Rails. I'll explain further down the page.
Your associations will be the likely cause of the problem.
Setting up complicated associations is always tricky - it's best to keep the data as separate as possible.
Here's how I'd construct the models / associations:
#app/models/university_student.rb
class UniversityStudent < ActiveRecord::Base
belongs_to :university
belongs_to :student, class_name: "User" #-> student_id
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :placements, class_name: "UniversityStudent", foreign_key: :student_id #-> user.placements
has_many :universities, through: :placements #-> user.universities
has_and_belongs_to_many :projects #-> user.projects
has_one :profile #-> user.profile (avatar etc)
has_many :created_projects, class_name: "Project", foreign_key: :creator_id
end
#app/models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user #-> store avatar here. This can be used across entire app
end
#app/models/university.rb
class University < ActiveRecord::Base
has_many :projects
has_many :students, class_name: "UniversityStudent" #-> university.students
end
#app/models/project.rb
class Project < ActiveRecord::Base
belongs_to :university
belongs_to :creator, class_name: "User" #-> creator_id
has_and_belongs_to_many :users
delegate :profile, to: :creator, prefix: true #-> #project.creator_profile
end
This allows you to do the following:
def create
#project = curent_user.created_projects.new project_params
#project.users << current_user
Because the associations actually associate your data, you'll be able to do the following:
def show
#project = Project.find params[:id]
##creator_profile = #project.creator.profile
#creator_profile = #project.creator_profile #-> if you use the delegate method outlined in the models
end
--
In my projects, show, I want to display the university that the project creator belongs to.
#app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
def show
##project = Project.find params[:id]
#project = current_user.created_projects.find params[:id]
end
end
#app/views/projects/show.html.erb
<%= #project.creator.universities.first %>
My code above allows for multiple universities. Thinking about it, it should be limited to one, but I'll leave it as is for now, maybe change it later.
In my uni table, I have attributes called logo and name. I use avatar uploader in which i have logo defined (that's why I have two .logo below).
Don't use two logo method, it's an antipattern (explained below)
The fix for this is two-fold:
Firstly, make sure you're calling #creator_profile.university with the following:
<%= #creator_profile.university %>
If this works, it means you have a problem with .logo.logo (detailed below), if it doesn't, it means you've not defined #creator_profile or the university association correctly.
Secondly, you need to ensure you have the correct controller/view setup.
The problem for many people - especially beginners - is they simply don't understand the way Rails works with controllers & views. You need to appreciate that each time you render a view, the only data it has access to is that which you define in the corresponding controller action...
#app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
def show
#project = Project.find params[:id]
#creator_profile = #project.creator_profile
end
end
#app/views/projects/show.html.erb
<%= content_tag :div, #creator_profile.universities.first.name, class: "generaltext" %>
Trivia
#project.creator_id = current_user.id
This should not have to be defined.
You should be able to change the foreign_key in the association, so that Rails will automagically define the creator_id for you:
#app/models/project.rb
class Project < ActiveRecord::Base
belongs_to :creator, class: "User" #-> foreign_key should be :creator_id
end
#app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
def create
#project = current_user.created_projects.new project_params #-> populates foreign key automatically.
--
.logo.logo
This is an antipattern.
Calling the same method twice is simply bad practice - why are you doing it?
You either want to delegate any recursive data you're trying to access (such as the example with .creator_profile above), or you'll want to restructure that functionality.
You want the following:
If you have to delegate to an assets model, you could get away with the following:
<%= #creator_profile.university.images.logo %>

Validate presence of polymorphic parent

I am developing a Rails 3.2 application with the following models:
class User < ActiveRecord::Base
# Associations
belongs_to :authenticatable, polymorphic: true
# Validations
validates :authenticatable, presence: true # this is the critical line
end
class Physician < ActiveRecord::Base
attr_accessible :user_attributes
# Associations
has_one :user, as: :authenticatable
accepts_nested_attributes_for :user
end
What I am trying to do is validate whether a user always has an authenticatable parent. This works fine in itself, but in my form the user model complains that the authenticatable is not present.
I am using the following controller to show a form for a new physician which accepts nested attributes for the user:
def new
#physician = Physician.new
#physician.build_user
respond_to do |format|
format.html # new.html.erb
format.json { render json: #physician }
end
end
And this is my create method:
def create
#physician = Physician.new(params[:physician])
respond_to do |format|
if #physician.save
format.html { redirect_to #physician, notice: 'Physician was successfully created.' }
format.json { render json: #physician, status: :created, location: #physician }
else
format.html { render action: "new" }
format.json { render json: #physician.errors, status: :unprocessable_entity }
end
end
end
On submitting the form, it says that the user's authenticatable must not be empty. However, the authenticatable_id and authenticatable_type should be assigned as soon as #physician is saved. It works fine if I use the same form to edit a physician and its user, since then the id and type are assigned.
What am I doing wrong here?
I believe this is expected:
https://github.com/rails/rails/issues/1629#issuecomment-11033182 ( last two comments).
Also check this out from rails api:
One-to-one associations
Assigning an object to a has_one association automatically saves that
object and the object being replaced (if there is one), in order to
update their foreign keys - except if the parent object is unsaved
(new_record? == true).
If either of these saves fail (due to one of the objects being
invalid), an ActiveRecord::RecordNotSaved exception is raised and the
assignment is cancelled.
If you wish to assign an object to a has_one association without
saving it, use the build_association method (documented below). The
object being replaced will still be saved to update its foreign key.
Assigning an object to a belongs_to association does not save the
object, since the foreign key field belongs on the parent. It does not
save the parent either.
and this
build_association(attributes = {}) Returns a new object of the
associated type that has been instantiated with attributes and linked
to this object through a foreign key, but has not yet been saved.
You have to create a Parent first. Then assign it's id to polymorphic object.
From what I can see, you create an object Physician.new which builds User but at this point it's not saved yet, so it doesn't have an id, so there is nothing to assign to polymorphic object. So validation will always fail since it's called before save.
In other words: In your case when you call build_user, it returns User.new NOT User.create . Therefore authenticatable doesn't have a authenticatable_id assigned.
You have several options:
Save associated user first.
OR
Move validation in to after_save callback ( Possible but very annoying and bad)
OR
Change your app structure - maybe avoid polymorphic association and switch to has_many through? Hard for me to judge since I don't know internals and business requirements. But it seems to me this is not a good candidate for polymorphic association. Will you have more models than just User that will be authenticatable?
IMHO the best candidates for polymorphic associations are things like Phones, Addresses, etc. Address can belong to User, Customer, Company, Organization, Area51 etc, be Home, Shipping or Billing category i.e. It can MORPH to accommodate multiple uses, so it's a good object to extract. But Authenticatable seems to me a bit contrived and adds complexity where there is no need for it. I don't see any other object needing to be authenticable.
If you could present your Authenticatable model and your reasoning and maybe migrations (?) I could advise you more. Right now I'm just pulling this out of thin air :-) But it seems like a good candidate for refactoring.
You can just move validation to before_save callback and it will work fine:
class User < ActiveRecord::Base
# Associations
belongs_to :authenticatable, polymorphic: true
# Validations
before_save :check_authenticatable
def check_authenticatable
unless authenticatable
errors[:customizable] << "can't be blank"
false
end
end
end
In the create action, I had to assign it manually:
#physician = Physician.new(params[:physician])
#physician.user.authenticatable = #physician
My problem is a little different (has_many and with different validation), but I think this should work.
I was able to get this to work by overriding the nested attribute setter.
class Physician
has_one :user, as: :authenticatable
accepts_nested_attributes_for :user
def user_attributes=(attribute_set)
super(attribute_set.merge(authenticatable: self))
end
end
To DRY it up, I moved the polymorphic code to a concern:
module Authenticatable
extend ActiveSupport::Concern
included do
has_one :user, as: :authenticatable
accepts_nested_attributes_for :user
def user_attributes=(attribute_set)
super(attribute_set.merge(authenticatable: self))
end
end
end
class Physician
include Authenticatable
...
end
For has_many associations, the same can be accomplished with a map:
class Physician
has_many :users, as: :authenticatable
accepts_nested_attributes_for :users
def users_attributes=(attribute_sets)
super(
attribute_sets.map do |attribute_set|
attribute_set.merge(authenticatable: self)
end
)
end
end
class User
belongs_to :authenticatable, polymorphic: true
validates :authenticatable, presence: true
end
All that said, I think konung's last comment is correct - your example does not look like a good candidate for polymorphism.
I'm not sure if this solves your problem, but I use something like this when validating that a polymorphic parent exists.
Here is some code that I used in a video model with the parent as the polymorphic association. This went in video.rb.
validates_presence_of :parent_id, :unless => Proc.new { |p|
# if it's a new record and parent is nil and addressable_type is set
# then try to find the parent object in the ObjectSpace
# if the parent object exists, then we're valid;
# if not, let validates_presence_of do it's thing
# Based on http://www.rebeccamiller-webster.com/2011/09/validate-polymorphic/
if (new_record? && !parent && parent_type)
parent = nil
ObjectSpace.each_object(parent_type.constantize) do |o|
parent = o if o.videos.include?(p) unless parent
end
end
parent
}

Child not being created from Parent model?

I have a checkbox that if checked allows my child resource called Engineer to be created. I'm trying to create it through my model since that's where I can call the after_save method.
Here is my code:
models/user.rb
class User < ActiveRecord::Base
has_many :armies
has_many :engineers
end
models/army.rb
class Army < ActiveRecord::Base
has_many :engineers
attr_reader :siege
after_save :if_siege
private
def if_siege
if self.siege
Engineer.create!( :user_id => current_user.id, :army_id => self.id )
end
end
end
models/engineer.rb
class Engineer < ActiveRecord::Base
belongs_to :user
belongs_to :army
end
controllers/armies_controller.rb
def new
#army = Army.new
end
def create
#army = current_user.armies.build(params[:army])
if #army.save
redirect_to new_army_path
else
render :new
end
end
end
This gives me an error though for my if_siege method:
undefined local variable or method `current_user'
How can I fix this or is there another way to do this? Not sure if this should go in the controller or model but I only can wrap my head around putting this in the model.
Thanks.
Add belongs_to :user to the Army model
In Army#if_siege, update Engineer.create! as follows
Engineer.create!( :user_id => self.user.id, :army_id => self.id )
First, the current_user object won't exist within the context of the Model layer unless your authentication is doing something to make it available. This is usually a non Threadsafe approach though. Maybe for you this isn't the issue.
Current User Instantiation
Having said that, one way (perhaps not the ideal way) to address this is by creating an attr_accessor in the model on the object called Army. Then set the current_user to this in the Army new action in the controller where the current_user instance is available.
# in the Army model
attr_accessor :the_user
# in the Army Controller
#army = Army.new(:the_user => current_user.id)
You will also have to add a hidden field to store this value in your view to carry this through to the create action.
Just an observation, but I'm fairly sure in the "if_seige" method the self calls are redundant. self should already be scoped to the Army object in that method.

rails 3 has_many :through record save error

I'm not exactly sure what my problem is, so this question may require some more clarification, but here's what seems to be most relevant:
I have a has_many :through and the join model has some fields that aren't foreign keys. When I build the models up and try to save I get a validation error on the non-foreign key fields from the join model.
My files look like:
Person.rb
has_many :wedding_assignments, :dependent => :destroy
has_many :weddings, :through=>:wedding_assignments
accepts_nested_attributes_for :weddings
accepts_nested_attributes_for :wedding_assignments
Wedding.rb
has_many :wedding_assignments, :dependent => :destroy
has_many :people, :through=>:wedding_assignments
accepts_nested_attributes_for :people
accepts_nested_attributes_for :wedding_assignments
WeddingAssignment.rb
belongs_to :person
belongs_to :wedding
validates_presence_of :role, :person, :wedding
(role is a string)
people_controller.rb
def new
#person = Person.new
1.times do
wedding = #person.weddings.build
1.times do
assignment = wedding.wedding_assignments.build
assignment.person = #person
assignment.wedding = wedding
end
end
end
def create
#person = Person.new(params[:person])
#person.weddings.each do |wedding|
wedding.wedding_assignments.each do |assignment|
assignment.person = #person #i don't think I should need to set person and wedding manually, but I get a validation error if I don't
assignment.wedding = wedding
end
end
end
the params that come back look like:
{"first_name"=>"", "last_name"=>"", "weddings_attributes"=>{"0"=>{"wedding_assignments_attributes"=>{"0"=>{"role"=>"Bride's Maid", "budget"=>""}}, "date"=>"", "ceremony_notes"=>""}}}
And the exact error is:
ActiveRecord::RecordInvalid in PeopleController#create
Validation failed: Role can't be blank
Which is clearly not correct, since you can see it in params[]
What am I doing wrong?
This is rails 3.0.0
Right, this is a bit of a guess, so apologies if I wind up wasting your time here...
It looks to me like in your create method, you're creating the 'wedding' relationship (which is only a 'pretend' relationship really, has it's using :through => :wedding_assignments), and then returning this. You're then asking rails to re-create these objects in your call to Person.new. My guess is that rails is getting confused by trying to create an object at the far side of a has_many :through without the intermediate object being present.
I would be tempted to restructure this a little (untested code!):
def new
#person = Person.new
#wedding = Wedding.new
#wedding_assignment = WeddingAssignment.new
end
def create
#person = Person.new(params[:person])
#wedding = Wedding.new(params[:person])
#assignment = WeddingAssignment.new(params[:wedding_assignment].merge({:person => #person}))
end
I've got a feeling this'll work until the last line. I suspect to get that to work you might need to use transactions:
def create
#person = Person.new(params[:person])
#wedding = Wedding.new(params[:person])
ActiveRecord::Base.transaction do
if #person.valid? && #wedding.valid?
[#person,#wedding].each.save!
#assignment = WeddingAssignment.new(params[:wedding_assignment].merge({:person => #person}))
#assignment.save!
end
end
end
This ought to ensure that everything is created in the right order and IDs are available at the right times etc. Unfortunately though, it's a bit more complicated than your example, and does mean that you'll struggle to support multiple weddings.
Hope this helps, and doesn't wind up being a blind alley.
Try changing "Person.new" to "Person.create", maybe creating the record in the db right away will help with the associations.

Resources