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.
Related
Model:
class UserPosition < ApplicationRecord
belongs_to :user
belongs_to :job_title
end
UserPosition's schema:
t.integer :user_id
t.integer :company_id
t.integer :industry_id
t.integer :department_id
t.integer :job_title_id
t.string :job_title_custom
user_positions_controller.rb
def create
#user_position = UserPosition.find_or_create_by(user_id: current_user.id)
#user_position.update_attributes({
:industry_id => params[:industry_id],
:department_id => params[:department_id],
:job_title_id => params[:job_title_id],
:job_title_custom => params[:job_title_custom]
})
I need UserPosition to either create a record with:
user_id
job_title_custom
OR
t.integer :user_id
t.integer :company_id
t.integer :industry_id
t.integer :department_id
t.integer :job_title_id
Currently, if I try to create a UserPosition with just user_id & job_title_custom
It doesn't work, the logs show ROLLBACK the error message is:
#messages={:job_title=>["must exist"]}
What am I doing wrong here? I think it could be because job_title has a relationship defined in the model but the Rails Guide says that they are optional, so I'm not sure.
Turns out this is a new Rails 5 behavior.
"In Rails 5, whenever we define a belongs_to association, it is required to have the associated record present by default after this change.
It triggers validation error if associated record is not present."
"In Rails 4.x world To add validation on belongs_to association, we need to add option required: true ."
"Opting out of this default behavior in Rails 5. We can pass optional: true to the belongs_to association which would remove this validation check."
Full Answer: http://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html
In a Rails ( 4.1.5 / ruby 2.0.0p481 / win64 ) application I have a many-to-many relationship between Student and Course and a join model StudentCourse which represents the association, which has an additional attribute called "started", which is set by default on "false".
I also have added an index in the join table made of the student_id and the course_id, and set a unique check on that, like this
t.index [:student_id, :course_id], :unique => true, :name => 'by_student_and_course'
Now I see that associations are created by either doing:
Student.first.courses.create(:name => "english")
or
Course.first.students << Student.first
This is fine and it's the expected behaviour, I suppose.
What I am looking after is the correct way to get and set the "started" attribute.
I am seeing an odd behaviour when accessing that attribute from the other models and not straight from the join model.
s = Student.create
c = Course.create(:name => "english")
s.student_courses.first
=> | "english" | false | # (represented as a table for practicity)
s.student_courses.first.started = true
=> | "english" | true |
s.save
=> true
Ok this looks like it has been saved but when I loot ak:
StudentCourse.first
=> | 1 | 1 | false |
So it is set on true if I go through the student nested attributes, but it's still false in the join model. I also tried doing "reload!" but it makes no difference and they will mantaint their own different value.
If something is going so bad that values are not actually persisted I should be told instead of getting "true" when saving, because otherwise how bad could be the consequences of this ? What am I missing here?
Anyway, if I try modifying the "started" attribute on the join model directly, I meet another kind of problem:
StudentCourse.first.started = true
StudentCourse Load (1.0ms) SELECT "student_courses".* FROM "student_courses" LIMIT 1
=> true
StudentCourse.first.started
=> false
It has not changed!
StudentCourse.find_by(:student_id => "10", :course_id => "1").started = true
=> true
StudentCourse.find_by(:student_id => "10", :course_id => "1").started
=> false
Same as before.. I try with:
StudentCourse.find(1).started = true
ActiveRecord::UnknownPrimaryKey: Unknown primary key for table student_courses in model StudentCourse.
Then with:
sc = StudentCourse.first
sc.started = true
=> true
sc
=> | 1 | 1 | true |
seems great but when saving:
sc.save
(0.0ms) begin transaction
SQL (1.0ms) UPDATE "student_courses" SET "started" = ? WHERE
"student_courses"."" IS NULL [["started", "true"]]
SQLite3::SQLException: no such column: student_courses.: UPDATE
"student_courses" SET "started" = ? WHERE "student_courses"."" IS NULL
(1.0ms) rollback transaction ActiveRecord::StatementInvalid:
SQLite3::SQLException: no such column: student_courses.: UPDATE
"student_courses" SET "started" = ? WHERE "student_courses"."" IS
NULL from
C:/Ruby200-x64/lib/ruby/gems/2.0.0/gems/sqlite3-1.3.9-x64-mingw32/lib/sqlite3/database.rb:91:in
`initialize'
So I think this all has to do with not having a primary key in
join-table?
But I am not sure enough on how to use it and if that'd represent a
good practice for the case I am trying to solve ?
Also, if this is the problem, why then I don't get the same warning
here when I save the student after I do
s.student_courses.first.started = true, as shown in the examples
above?
Code
student.rb
class Student < ActiveRecord::Base
has_many :student_courses
has_many :courses, :through => :student_courses
end
course.rb
class Course < ActiveRecord::Base
has_many :student_courses
has_many :students, :through => :student_courses
end
student_course.rb
class StudentCourse < ActiveRecord::Base
belongs_to :course
belongs_to :student
end
schema.rb
ActiveRecord::Schema.define(version: 20141020135702) do
create_table "student_courses", id: false, force: true do |t|
t.integer "course_id", null: false
t.integer "student_id", null: false
t.string "started", limit: 8, default: "pending", null: false
end
add_index "student_courses", ["course_id", "student_id"], name: "by_course_and_student", unique: true
create_table "courses", force: true do |t|
t.string "name", limit: 50, null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "students", force: true do |t|
t.string "name", limit: 50, null: false
t.datetime "created_at"
t.datetime "updated_at"
end
end
create_join_table.rb (migration for join table)
class CreateJoinTable < ActiveRecord::Migration
def change
create_join_table :courses, :students, table_name: :student_courses do |t|
t.index [:course_id, :student_id], :unique => true, :name => 'by_course_and_student'
t.boolean :started, :null => false, :default => false
end
end
end
Ok I finally got what was going on here:
If you create a join table in a migration using #create_join_table, this method will not create the default primary key called "id" (and not add an index for it) which is what rails does by default when using #create_table.
ActiveRecord needs a primary key to build its queries, because it is the column that it will be used by default when doing things like Model.find(3).
Also if you think you can get around this by doing something like StudentCourse.find_by(:course_id => "1", :student_id => "2").update_attributes(:started => true) [0] it will still fail, because after the record it's found, AR will still try to update it looking at the "id" of the record it found.
Also StudentCourse.find_by(:course_id => "1", :student_id => "2").started = true will retrun true but of course it is not saved until you call #save on it. If you assign it to a var relationship and then you call relationship.save you will see it will fail to save for the above reasons.
[0]
In the join table I didn't want duplicate records for a "student_id" and "course_id" so in the migration I had explicitely added a unique constraint for them (using unique index).
This led me to think that I did not need anymore a primary key to uniquely identify a record, because I had those two values... I thought that adding an index on them was enough for they to work as a primary key... but it is not. You need to explicitely define a primary-key when you are not using the default "id" one.
Also turns out that Rails does not support composite primary keys and so even if I wanted to add a primary key build on those two values (so making them primary-key and unique-index, like default rails "id" works) it would have not been possible.
A gem for that exists: https://github.com/composite-primary-keys/composite_primary_keys
So, end of the story, the way I fixed it was simply adding t.column :id, :primary_key to the migration for the join table creation. Also I could have not created the join table with #create_join_table but instead using just #create_table (which would create an "id" automatically").
Hope this helps someone else.
Also this answer to another question was very helpful, thank you #Peter Alfvin !
OK, it appears that you don't have a primary key (we are getting confirmation shortly) in your join table. You do need to have a primary key when trying to access the join table.
I would suggest your migration be:
class CreateStudentCourses < ActiveRecord::Migration
def change
create_table :student_courses do |t|
t.references :course
t.references :student
t.boolean :started, default: false
t.timestamps
t.index [:student_id, :course_id], :unique => true, :name => 'by_student_and_course'
end
end
end
The model definitions look good, so that would be the only change I can see that needs to be made.
After that, doing what you have been doing should work correctly. You would create the join and then access it after the creation. If you want to assign the boolean to true upon creation, you would need to create the record through the StudentCourse model with the information you need (student_id, course_id and started = true) instead of through either association.
StudentCourse.create(course_id: course.id, student_id: student.id, started: true)
s = Student.create
c = Course.create(:name => "english")
s.student_courses.first.started = true
s.save
I think the clue here is in the first line that you posted (represented above). s is an instance of the student and when you call s.save then you're asking the student to save any changes to its attributes. There are not any changes to save, however, because you made a change to an association.
You have a couple of options. If you prefer the direct access approach from your code snippet then the following should work.
s = Student.create
c = Course.create(:name => 'english')
s.courses << c
s.student_courses.first.update_attributes(:started => true)
Another alternative would be to use the accepts_nested_attributes_for macro to expose the started attribute from the student perspective.
class Student
has_many :student_courses, :inverse_of => :student
has_many :courses, :through => :student_courses
accepts_nested_attributes_for :student_courses
end
s = Student.create
c = Course.create(:name => 'english')
s.courses << c
s.update_attributes(:student_courses_attributes=>[{:id => 1, :started => true}])
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.
I have built the following model to handle user's message exchange:
create_table "messages", :force => true do |t|
t.integer "source_id"
t.integer "destination_id"
t.string "object"
t.string "body"
t.datetime "created_at"
t.datetime "updated_at"
end
These are its associations:
class Message < ActiveRecord::Base
belongs_to :sender, :class_name=>'User', :foreign_key=>'source_id'
belongs_to :reciever, :class_name=>'User', :foreign_key=>'destination_id'
end
And these other are the associations on the other side (the user model):
has_many :sent_messages, :class_name=> 'Message', :foreign_key=>'source_id', :dependent=>:destroy
has_many :recieved_messages, :class_name=> 'Message', :foreign_key=>'destination_id', :dependent=>:destroy
The model is correct and work properly, in fact from the message I can retrieve who is the sender and who is the receiver and from the user, I can get all the sent and received messages. Unfortunately, It does not handle any situation: What if the receiver or the sender delete the message ? The message is unique so it disappear in both sides (bad thing). How to know if one of the side had already read the message ?
Any suggestion ? Do you think I have to replan the model ?
Tnx
this is a nice problem! I would model that to compare as closely as possible to the e-mail model. So a message always belongs to a single user, and it was either sent or received.
In short:
create_table "messages", :force => true do |t|
t.integer :user_id
t.string :subject
t.string :body
t.boolean :sent
end
And the model would like:
class Message < ActiveRecord::Base
belongs_to :user
scope :sent, where(:sent => true)
scope :received, where(:sent => false)
end
And in the user:
class User
has_many :messages
end
You would then simply be able to query all sent messages by
user.messages.sent
and the received messages
user.messages.received
Sending a message does become a bit more complicated then:
class Message
def send_message(from, recipients)
recipients.each do |recipient|
msg = self.clone
msg.sent = false
msg.user_id = recipient
msg.save
end
self.update_attributes :user_id => from.id, :sent => true
end
end
or something along those lines: you copy the message and attach it to all recipients, and lastly make the original message the sent message.
This way each user has total control over the message.
Possible improvements:
also keep an explicit reference to the sender and receiver(s) in the message, to be able to allow replies and stuff
instead of working with a single boolean, maybe allow working with folders?
Hope this helps.
You can add two booleans to mark the message as deleted for both sender and receiver. Then after setting either of them check if the message can be deleted permanently.
Example:
create_table "messages", :force => true do |t|
t.boolean :sender_deleted
t.boolean :receiver_deleted
end
And in model:
class Message
def self.delete_message(id)
m = Message.find(id)
m.destroy if m.sender_deleted && m.receiver_deleted
end
end
You can nullify on a deleted record with :dependent=>:nullify
has_many :sent_messages, :class_name=> 'Message', :foreign_key=>'source_id', :dependent=>:nullify
has_many :recieved_messages, :class_name=> 'Message', :foreign_key=>'destination_id', :dependent=>:nullify
You'll need to handle when displaying the message that the sender/receiver of the message has been deleted, since the sender_id or destination_id will be null, but the message will stay intact.
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)