Associations in Rails - ruby-on-rails

i am new to Rails ..
i am having a Table named users (id,name)
and another table which has the additional information of the user called
user_details(id,user_id,additional_info) where additional_info is a hash .
In the User Model i added a line
has_one :user_details
And in the User_Detail model i added a line
belongs_to :user
serialize :additional_details, Hash
Now in the Users Controller i am having an action
# set_user_empid to set the hash value empid in the additional_info column for the current_user
def set_user_empid
#user1 = current_user
#user_detail1=#user1.user_details
#user_detail1.additional_details[:empid] = params[:value]
#user_detail1.save
render :text => CGI::escapeHTML(#user_detail1.additional_details[:empid].to_s)
end
The above one #user1.user_details shows me the error as
NameError (uninitialized constant User::UserDetails):
But the same thing if i change the has_one to has_many i am getting the actual result...
Please give suggestions...

The quick fix here is to change has_one :user_details to has_one :user_detail, but really what you want is to get rid of the UserDetail model entirely and just move the column into the User model, so the users table has these columns: id, name, additional_info and then move the call to serialize into the User model. No real reason to have a separate table just for metadata.

I believe since you are using user_details, pluralized, it is not able to pick it up. Could you try using has_one :user_detail

Related

How do I replace an ActiveRecord association member in-memory and then save the association

I have a has_may through association and I'm trying to change records in the association in memory and then have all the associations updated in a single transaction on #save. I can't figure out how to make this work.
Here's a simplifiction of what I'm doing (using the popular Blog example):
# The model
class Users < ActiveRecord::Base
has_many :posts, through: user_posts
accepts_nested_attributes_for :posts
end
# The controller
class UsersController < ApplicationController
def update
user.assign_attributes(user_params)
replace_existing_posts(user)
user.save
end
private
def replace_existing_posts(user)
user.posts.each do |post|
existing = Post.find_by(title: post.title)
next unless existing
post.id = existing
post.reload
end
end
end
This is a bit contrived. The point is that if a post that the user added already exists in the system, we just assign the existing post to them. If the post does not already exist we create a new one.
The problem is, that when I call user.save it saves any new posts (and the user_post association) but doesn't create the user_post association for the existing record.
I've tried to resolve this by adding has_many :user_posts, autosave: true to the User model, but despite the documented statement "When :autosave is true all children are saved", that doesn't reflect the behavior I see.
I can make this work, with something hacky like this, but I don't want to save the association records separately (and removing and replacing all associations would lead to lots of callback I don't want to fire).
posts = user.posts.to_a
user.posts.reset
user.posts.replace(posts)
I've trolled through the ActiveRecord docs and the source code and haven't found a way to add records to a has_many through association that create the mapping record in memory.
I finally got this to work, just by adding the association records manually.
So now my controller also does this in the update:
user.posts.each do |post|
next unless post.persisted?
user.user_posts.build(post: post)
end
Posting this as an answer unless someone has a better solution.

How to build objects that avoid a ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection erros in rails?

I am experiencing the ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection error when trying to build a new object in my rails app. It does not fit any of the standard errors I've seen, and can't be fixed with inverse_of associations.
I presume I need to run a callback to help this work - can anyone help fix the issue below:
def PhoneNumber do
belongs_to :key_contact
end
def KeyContact do
has_many :phone_numbers
has_many :sale_contacts
end
def SaleContact do
belongs_to :key_contact
belongs_to :sales_opportunity
has_many :phone_numbers, through: :key_contact
accepts_nested_attributes_for :phone_numbers
end
As you can see, SaleContact is the join table where key_contacts and sales_opportunities meet - basically I'm picking existing key_contacts and displaying them on a sales_opportunity page with some additional details (role, preference etc - I've excluded this for brevity).
When adding a new sale_contact I want to offer users the ability to also add phone_numbers at the same time. This is throwing my activerecord error.
My SaleContact Controller:
def new
#sale_contact = SaleContact.new
#phone_number = #sale_contact.phone_numbers.build
end
This works to show the fields_for phone_number on the input form, and passed the right attributes through the params hash for adding a new phone_number, but that's when I get the error:
ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection (Cannot modify association 'SaleContact#phone_numbers' because the source reflection class 'PhoneNumber' is associated to 'KeyContact' via :has_many.):
From what I can see:
My new controller action builds a phone_number, but because the sale_contact does not yet know which key_contact it's associated with I presume ActiveRecord gets confused
If I try and remove the #sale_contact.phone_number.build line (replacing it with PhoneNumber.new for example) the fields no longer appear on the SaleContact new form
As such I was thinking of creating a callback to strip out the phone_number_attributes from the sale_contact hash, destroy the newly built phone_number and all associations, then start fresh by passing the phone_numbers_attributes to a PhoneNumber.new(phone_number_attributes) action and saving as a separate transaction. Would that work?
You could try this instead:
delegate :phone_numbers, to: :key_contact

Updating an ActiveAdmin nested resource using inputs not located on the table

I currently have a form for a nested resource in one of my activeadmin pages:
f.inputs "Courses" do
f.has_many :registrations, :allow_destroy => true, new_record: true do |tc|
tc.input :course
tc.input :semester
end
end
This is for a student resource. A student has many course_offerings through registrations. However, I didn't want users to select the course_offering directly from a menu (Since there will be many repeats of the same course each year). Instead there is an input for course and semester. course and semester are instance variables on the registration model. They are set in the form, then the correct course_offering is found in an after save hook and associated with the registration. The code is as follows:
def semester=(s)
#semester = s.to_i
end
def semester
self.course_offering.semester
end
def course=(co)
#course = co.to_i
end
def course
self.course_offering.course
end
before_save :set_course_offering
def set_course_offering
self.course_offering = CourseOffering.where(semester_id: #semester, course_id: #course).first
#TODO: Handle case where no course offering is found
end
I am having two problems. The first is that I get a nil pointer error when registrations table is empty.
undefined method `course' for nil:NilClass
I have accepts_nested_attributes call in my student model.
accepts_nested_attributes_for :registrations, :allow_destroy => true
which is the only suggestion I get when looking up the error but still get it despite having that piece of code. It seems to work fine when I remove the course and semester and replace it with a course_offering instead.
The next problem I have is that the student record does not save after hitting update. I assume this is because I don't make any changes that write to the database when I only update the two course and semester instance variables only. I either need to update another input or add the call to the semester= method.
You get an undefined method error because when the registration table is empty there are no course offerings associated with your student, so in the accessor method for course you get nil for self.course_offerings. You could try this instead which takes the nil value into account:
def course
self.course_offering.try(:course)
end
You don't need the accept_nested_attributes_for because you don't want to create or modify course offerings through students.
For your second problem: you're right about the dirty tracking. Your model is not saved because your student model has not been modified from the perspective of ActiveRecord. You need to flag an attribute (eg course_offering) as dirty by hand with the course_attribute_will_change! method before saving the model.
Although this situation looks like a good example to introduce form objects. There is a great library for that called reform.

Rails: Manipulating params before saving an update - wrong approach?

First, I feel like I am approaching this the wrong way, but I'm not sure how else to do it. It's somewhat difficult to explain as well, so please bear with me.
I am using Javascript to allow users to add multiple text areas in the edit form, but these text areas are for a separate model. It basically allows the user to edit the information in two models rather than one. Here are the relationships:
class Incident < ActiveRecord::Base
has_many :incident_notes
belongs_to :user
end
class IncidentNote < ActiveRecord::Base
belongs_to :incident
belongs_to :user
end
class User < ActiveRecord::Base
has_many :incidents
has_many :incident_notes
end
When the user adds an "incident note", it should automatically identify the note with that particular user. I also want multiple users to be able to add notes to the same incident.
The problem I ran into is that when a user adds a new text area, rails isn't able to figure out that the new incident_note belongs_to the user. So it ends up creating the incident_note, but the user_id is nil. For example, in the logs I see the following insert statement when I edit the form and add a new note:
INSERT INTO "incident_notes" ("created_at", "updated_at", "user_id", "note", "incident_id") VALUES('2010-07-02 14:09:11', '2010-07-02 14:09:11', NULL, 'Another note', 8)
So what I've decided to try to do is manipulate the params for :incident in the update method. This way I can just add the user_id myself, however this seems un-rails-like, but I'm not sure how else to it.
When the form is submitted, the parameters look like this:
Parameters: {"commit"=>"Update", "action"=>"update", "_method"=>"put", "authenticity_token"=>"at/FBNxjq16Vrk8/iIscWn2IIdY1jtivzEQzSOn0I4k=", "id"=>"18", "customer_id"=>"4", "controller"=>"incidents", "incident"=>{"title"=>"agggh", "incident_status_id"=>"1", "incident_notes_attributes"=>{"1279033253229"=>{"_destroy"=>"", "note"=>"test"}, "0"=>{"id"=>"31", "_destroy"=>"", "note"=>"asdf"}}, "user_id"=>"2", "capc_id"=>"SDF01-071310-004"}}
So I thought I could edit this section:
"incident_notes_attributes"=>{"1279033253229"=>{"_destroy"=>"", "note"=>"test"}, "0"=>{"id"=>"31", "_destroy"=>"", "note"=>"another test"}}
As you can see, one of them does not have an id yet, which means it will be newly inserted into the table.
I want to add another attribute to the new item so it looks like this:
"incident_notes_attributes"=>{"1279033253229"=>{"_destroy"=>"", "note"=>"test", "user_id" => "2"}, "0"=>{"id"=>"31", "_destroy"=>"", "note"=>"another test"}}
But again this seems un-rails-like and I'm not sure how to get around it. Here is the update method for the Incident controller.
# PUT /incidents/1
# PUT /incidents/1.xml
def update
#incident = #customer.incidents.find(params[:id])
respond_to do |format|
if #incident.update_attributes(params[:incident])
# etc, etc
end
I thought I might be able to add something like the following:
params[:incident].incident_note_attributes.each do |inote_atts|
for att in inote_atts
if att.id == nil
att.user_id = current_user.id
end
end
end
But obviously incident_note_attributes is not a method. So I'm not sure what to do. How can I solve this problem?
Sorry for the wall of text. Any help is much appreciated!
I have a similar requirement and this is how I tackled it:
class Incident < ActiveRecord::Base
has_many :incident_notes
belongs_to :user
attr_accessor :new_incident_note
before_save :append_incident_note
protected
def append_incident_note
self.incident_notes.build(:note => self.new_incident_note) if !self.new_incident_note.blank?
end
end
and then in the form, you just use a standard rails form_for and use the new_incident_note as the attribute.
I chose this method because I knew it was just throwing data into the notes with minor data validations. If you have in depth validations, then I recommend using accepts_nested_attributes_for and fields_for. That is very well documented here.

nested form & habtm

I am trying to save to a join table in a habtm relationship, but I am having problems.
From my view, I pass in a group id with:
<%= link_to "Create New User", new_user_url(:group => 1) %>
# User model (user.rb)
class User < ActiveRecord::Base
has_and_belongs_to_many :user_groups
accepts_nested_attributes_for :user_groups
end
# UserGroups model (user_groups.rb)
class UserGroup < ActiveRecord::Base
has_and_belongs_to_many :users
end
# users_controller.rb
def new
#user = User.new(:user_group_ids => params[:group])
end
in the new user view, i have access to the User.user_groups object, however when i submit the form, not only does it not save into my join table (user_groups_users), but the object is no longer there. all the other objects & attributes of my User object are persistent except for the user group.
i just started learning rails, so maybe i am missing something conceptually here, but i have been really struggling with this.
Instead of using accepts_nested_attributes_for, have you considered just adding the user to the group in your controller? That way you don't need to pass user_group_id back and forth.
In users_controller.rb:
def create
#user = User.new params[:user]
#user.user_groups << UserGroup.find(group_id_you_wanted)
end
This way you'll also stop people from doctoring the form and adding themselves to whichever group they wanted.
What does your create method look like in users_controller.rb?
If you're using the fields_for construct in your view, for example:
<% user_form.fields_for :user_groups do |user_groups_form| %>
You should be able to just pass the params[:user] (or whatever it is) to User.new() and it will handle the nested attributes.
Expanding on #jimworm 's answer:
groups_hash = params[:user].delete(:groups_attributes)
group_ids = groups_hash.values.select{|h|h["_destroy"]=="false"}.collect{|h|h["group_id"]}
That way, you've yanked the hash out of the params hash and collected the ids only. Now you can save the user separately, like:
#user.update_attributes(params[:user])
and add/remove his group ids separately in one line:
# The next line will add or remove items associated with those IDs as needed
# (part of the habtm parcel)
#user.group_ids = group_ids

Resources