Custom update lookup for accepts_nested_attributes_for - ruby-on-rails

I have a mostly static table of devices like so:
class CreatePlatforms < ActiveRecord::Migration
def change
create_table :platforms do |t|
t.column :model, :string, :null => false
t.column :name, :string, :null => false
t.timestamps
end
add_index :platforms, :model, :unique => true
Platform.create :model => "iPhone1,1", :name => "Original iPhone"
Platform.create :model => "iPhone1,2", :name => "iPhone 3G"
[...]
end
end
And a table, devices, that references platforms. Now I would like to send the model to the server, and have the created device linked to the corresponding id of that model in the database, similar to accepts_nested_attributes_for :platform. However, that creates the record unless there is an id in the attributes.
Is there any way with accepts_nested_attributes_for, or something similar, to use a different attribute to look up existing records?
I could manually swap it out in the controller like the following, but that is quit messy and a last resort:
params[:device][:platform] = Platform.find_by_model params[:device][:platform_attributes][:model]

I found a solution:
def autosave_associated_records_for_platform
if new_platform = Platform.find_by_model(platform.model) then
self.platform = new_platform
else
self.platform.name = "Unknown"
self.platform.save!
end
end

Related

How to map one user to another (from same table)

I'd like to know how to solve this problem in my model/migrations, with correct referential integrity/uniqueness constraints.
I have a user table with two types of user: support_worker and service_user (like teacher and pupil). A support_worker can provide support for many service_users. I used to have separate tables for these respective user types, but for simplicity it makes more sense to have both user types in a single 'user' table (for Devise).
I'll have another table called support_allocation which records the relationship between a support_worker and the service_user(s) they support - this support_allocation has other information stored about it (like a budget; time/money). So this table needs to map one user_id to another user_id. I imagine the table structure will look something like this: SupportAllocation (id, support_worker_id, service_user_id)
So far, my migrations look like this (I've used Devise gem to create the user table so this amends it):
class ChangeUsers < ActiveRecord::Migration[5.2]
def change
change_table :users do |t|
t.string :user_type # support_worker or service_user
t.string :given_name
t.string :family_name
t.string :customer_reference # only for service_users
t.date :date_of_birth # only for service_users
t.string :job_roles # only for support_workers
end
end
class CreateSupportAllocations < ActiveRecord::Migration[5.2]
def change
create_table :support_allocations do |t|
t.boolean :active, default: true
# This next bit is guesswork
t.integer support_worker_id # support_worker's user_id
t.integer service_user_id # service_user's user_id
t.timestamps
end
end
end
Here's where I get confused... I need to create a join, but this will only do it on user_id, whereas the relationship is defined by the two user_id columns (as shown and named above). I'm not sure if this a compound key or if a single foreign key (or two) will suffice.
Here's my migration work-in-progress:
class AddJoins < ActiveRecord::Migration[5.2]
def change
change_table :support_allocations do |t|
t.belongs_to :user, index: true
end
end
end
I'd like to know how to achieve this. For the record, I'm using ActiveAdmin for my app. Thank you for your help.
I don't think you need the AddJoins migration. Add 2 associations in your CreateSupportAllocations model like so:
belongs_to :support_worker, :foreign_key => :support_worker_id, :class_name => User
belongs_to :service_user, :foreign_key => :service_user_id, :class_name => User
In your activeadmin form you can set the collections for the select, for example
(in app/admin/support_allocations.rb)
form do |f|
f.inputs do
# your inputs
f.input :support_worker, :as => :select, :collection => User.where(:user_type => 'support_worker')
f.input :service_user, :as => :select, :collection => User.where(:user_type => 'service_user')
end
f.actions
end
# added after comments
index do
selectable_column
column :support_worker
actions
end
Add a to_s method in you user model like so:
def to_s
"#{self.full_name}"
end
Thanks for all your help. I added the suggested associations to my SupportAllocation model. For the record, I also had to add the following associations to my User model to make the join work fully, in both directions:
has_many :occurances_as_support_worker, :class_name => 'SupportAllocation', :foreign_key => 'support_worker_id'
has_many :occurances_as_service_user, :class_name => 'SupportAllocation', :foreign_key => 'service_user_id'
I used the example given here to work this out.
When accessing attributes specific to a type of user (i.e. using the join over support_worker_id OR service_user_id), on the index page. I use code like this:
column 'Service user', :full_name, :sortable => 'service_users.family_name' do |support_allocation|
#ServiceUser.find(support_allocation.service_user_id).full_name
support_allocation.service_user.full_name
end
column 'Support worker', :full_name, :sortable => 'support_workers.family_name' do |support_allocation|
support_allocation.support_worker.full_name
end

Trouble with self referential model in Rails

I have a model named User and I want to be able to self reference other users as a Contact. In more detail, I want a uni-directional relationship from users to other users, and I want to be able to reference an owned user of one user as a 'contact'. ALSO, i want to have information associated with the relationship, so I will be adding fields to the usercontact relation (I just edited this sentence in).
I attempted to do this while using the answer to this question as a guide.
Here is the User model:
user.rb
class User < ActiveRecord::Base
attr_accessible(:company, :email, :first_name, :last_name,
:phone_number, :position)
has_many(:user_contacts, :foreign_key => :user_id,
:dependent => :destroy)
has_many(:reverse_user_contacts, :class_name => :UserContact,
:foreign_key => :contact_id, :dependent => :destroy)
has_many :contacts, :through => :user_contacts, :source => :contact
end
I also created the model UserContact as a part of connecting contacts to users:
usercontact.rb
class UserContact < ActiveRecord::Base
belongs_to :user, :class_name => :User
belongs_to :contact, :class_name => :User
end
Here is the create_users.rb migration file i used:
create_users.rb
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :phone_number
t.string :email
t.string :company
t.string :position
t.timestamps
end
end
end
And here is the create_users_contacts.rb migration:
create_users_contacts.rb
class CreateUsersContacts < ActiveRecord::Migration
def up
create_table :users_contacts, :force => true do |t|
t.integer :user_id, :null => false
t.integer :contact_id, :null => false
t.boolean :update, :null => false, :default => false
end
# Ensure that each user can only have a unique contact once
add_index :users_contacts, [:user_id, :contact_id], :unique => true
end
def down
remove_index :users_contacts, :column => [:user_id, :contact_id]
drop_table :users_contacts
end
end
However, for reasons unknown to me, I believe something has gone awry in the linking since on my users index page, I have a column using <td><%= user.contacts.count %></td>, but I get this error from the line when I attempt to load the page:
uninitialized constant User::UserContact
I think the issue may be something to do with the fact that I want to name users associated with another user as contacts, because I cannot find other examples where that is done, and as far as I can tell I am doing everything properly otherwise (similarly to other examples).
The closest similar problem that I found was outlined and solved in this question. The issue was incorrect naming of his connecting model, however I double checked my naming and it does not have that asker's problem.
Any help is appreciated, let me know if any other files or information is necessary to diagnose why this is occurring.
EDIT
After changing usercontact.rb to user_contact.rb, I am now getting this error:
PG::Error: ERROR: relation "user_contacts" does not exist
LINE 1: SELECT COUNT(*) FROM "users" INNER JOIN "user_contacts" ON "...
^
: SELECT COUNT(*) FROM "users" INNER JOIN "user_contacts" ON "users"."id" = "user_contacts"."contact_id" WHERE "user_contacts"."user_id" = 1
EDIT TWO
The issue was that my linking table, users_contacts, was misnamed, and should have been user_contacts! so I fixed it, and now it appears to work!!
You need to rename your usercontact.rb to user_contact.rb
This is naming convention rails autoload works with.

Nested Attributes Update with a different Primary Key

I have 2 Models, I face error when i am updating them. I have used nested attributes.
class Channel < ActiveRecord::Base
self.primary_key = 'id'
has_many :channel_mappings , primary_key: 'channel_name', foreign_key: 'channel_name'
attr_accessible :channel_name, :channel_mappings_attributes
validates_presence_of :channel_name
accepts_nested_attributes_for :channel_mappings, :allow_destroy => true
end
2nd Model
class ChannelMapping < ActiveRecord::Base
self.primary_key = 'src_channel'
belongs_to :channel, primary_key: 'channel_name', foreign_key: 'channel_name'
attr_accessible :src_channel, :channel_name , :src_file_type
end
Update Method
def update
#channel = Channel.find(params[:id])
if #channel.update_attributes(params[:channel])
redirect_to #channel, notice: 'Channel was successfully updated.'
else
render action: 'edit'
end
end
Error
Type: ActiveRecord::RecordNotFound
Message: Couldn't find ChannelMapping with ID=ANY NAME for Channel with ID=2
I know it' something to do with Primary key overwritten. Any help will be useful
db/schema.rb
create_table "channels", :force => true do |t|
t.text "channel_name", :null => false
t.string "internal_flag", :limit => nil
t.string "exception_flag", :limit => nil
end
create_table "channel_mappings", :id => false, :force => true do |t|
t.text "src_channel", :null => false
t.text "channel_name", :null => false
end
You could try - #channel.attributes = params[:channel] instead of #channel.update_attributes(params[:channel])
This will also set all the attributes but without save.
Then you can call -
#channel.save
This will save your attributes.
The error seems to be record to be not found rather then update revert like.
Check the error log first and if needed then post it here if nothing works.
It would be better to use if else conditions as:
if #channel.save
#record saved
else
#error in save
end
Then you can know where it's going.
Well, in the first line of Channel.rb you're setting the primary key to be 'id'. So why are you specifying primary_key='channel_name' in your associations? That seems wrong.
Also, it will be helpful to see your definition of the channels table in db/schema.rb.
Update after additional information
In your gist, I see that your parameters contain an id key in channel_mappings_attributes. However , your schema.rb shows that channel_mappings doesn't have an id. That's the first thing you need to fix.

how to write this query of many-to-many base in rails

Hey guys
I'm new to rails, There's a lot of eye-opener for me, and I write some code and it seems no efficient, I paste my code below, could you help me find a better way to write this.
videos table:
class CreateVideos < ActiveRecord::Migration
def self.up
create_table :videos do |t|
t.string :title
t.string :desc
t.string :tudou
t.string :otherurl
t.timestamps
end
end
def self.down
drop_table :videos
end
end
drummers table:
class CreateDrummers < ActiveRecord::Migration
def self.up
create_table :drummers do |t|
t.string :first_name
t.string :middle_name
t.string :last_name
t.string :nick_name
t.boolean :gender
t.timestamps
end
end
def self.down
drop_table :drummers
end
end
and I set them to simple many-to-many association
class CreateDrummersVideosJoin < ActiveRecord::Migration
def self.up
create_table :drummers_videos, :id => false do |t|
t.integer "drummer_id"
t.integer "video_id"
end
end
def self.down
drop_table :drummers_videos
end
end
I want to find all the title of drummer first name is "Jojo" last name is "Mayer"'s video
my code:
title = Drummer.where(:first_name => "Jojo", :last_name => "Mayer").first.videos.each {|t| t.title}
This return all the column's data, not the only the title I want
and since the there's only one result return named "Jojo Mayer", But the return value is activeRelation, I can't call videos, so my work around is using :first to get the video instance in order to call the videos. I know it's definitely not the way doing it
any suggestion?
You need a join table in between them like you infer. But that join table in Rails does not have to be created in a migration. It can be done exclusively in the models.
#drummer.rb
belongs_to :drummer_videos, :polymorphic => true
has_many :videos, :as => :drummer_videos
#video.rb
belongs_to :drummer_videos, :polymorphic => true
has_many :drummers, :as => :drummer_videos
Make sure that the drummer TABLE and video TABLE have a drummer_videos_id attribute.
Then you can call your Drummer..
Drummer.where(:first_name => "Jojo", :last_name => "Mayer").videos.each {|t| t.title}
First off, if you want to get only the title attribute from the videos you should use map or collect instead of each, so something like this:
Drummer.where(...).first.videos.map{ |t| t.title }
or even shorter:
.map(&:title)
Second, it seems to me that if you really want to get all the videos from a single Drummer object, than using first in some way or another, like you do, is a pretty good option.
Otherwise, if you want to get all videos from different Drummers according to a certain criteria, then you should probably call Video directly and then join or include the Drummer. Perhaps like this:
Video.joins(:drummers).where("drummers.first_name = 'jojo' AND drummers.last_name = 'Mayer'").map(&:title)

Rails and Model Validation

I want to put some model level validation on the following table:
create_table :audios do |t|
t.integer :library_id, :null => false
t.string :file, :null => false, :limit => 2048
t.string :name, :limit => 512
t.timestamps
end
Does this mean, that my model, which (so far) looks like:
class Audio < ActiveRecord::Base
belongs_to :library
end
Has
class Audio < ActiveRecord::Base
validates_presence_of :library
...
or
class Audio < ActiveRecord::Base
validates_presence_of :library_id
...
?
To validate the presence of an association, use its name, without _id appended:
validates_presence_of :library
It will validate two things:
library_id is present
a Library with the given id exists
Using validates_presence_of :library_id will only give you the first validation of the two.
In addition to this, the version without _id will also correctly validate if both records are new (and therefore library_id is still unset).

Resources