DB fields not showing up in association custom queries? - ruby-on-rails

I have a notification list that the user can select different show options for different kinds of notifications along with how many results are returned. I'm keeping them in the user model because I want the custom sort to stay with the user between sessions. Here is the association in my user model:
has_many :notifications,
:class_name => "Notification",
:foreign_key => "user_id",
:conditions => ["won = ? and lost = ? and paid = ?", self.prefs_won, self.prefs_lost, self.prefs_paid],
:limit => self.prefs_results.to_s
But when I use the above code, Rails throws me an "unknown method" error for self.prefs_won. It is definitely a field in my database and set as a boolean value, but Rails can't find it... what's the problem?
EDIT:
Here's the migration:
t.boolean :prefs_won, :default => true
t.boolean :prefs_lost, :default => true
t.boolean :prefs_paid, :default => true
t.integer :prefs_results, :default => 10

ActiveRecord accessors for boolean attributes have a question mark at the end of the attribute name. Try self.prefs_won? instead.

Related

Rails console command to get a record when the foreign key pattern does not match the default pattern

Could you please show how to write the rails command when the association is created with different foreign key(author_id) deviating from default naming (user_id)?
In this example:
class User
has_many games
end
class Game
belongs_to :player_white, :class_name => "User", :foreign_key => "player_white_id"
belongs_to :player_black, :class_name => "User", :foreign_key => "player_black_id"
end
Rails console if I type the command:
>> User.games.
I get an error:
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column games.user_id does not exist
I understand that it is not able to find the foreign key user_id as the foreign key will be either player_white_id or player_black_id. But to specify that in rails console command?
I tried
User(:user_id => :player_white_id). But it does not work.
Game table in schema file:
create_table "games", force: true do |t|
t.integer "player_white_id"
t.integer "player_black_id"
t.string "name_for_game"
t.datetime "created_at"
t.datetime "updated_at"
end
Games has foreign keys "player_white_id" and "player_black_id" but actually belong to the same class User.
I am trying to get the games played by the player in rails console:
>>user1 =User.create(:username => "White", :password => "123")
>>user2 = User.create(:username => "black", :password => "123")
>> Game.create(:player_white_id => user1.id, :player_black_id => user2.id)
Now if I try to access
>>user1.games
It throws the above mentioned error.
Basically I want to get the list of games for a particular user. I know I can get by using the following query:
Game.where("player_white_id = ? or player_black_id = ?", user.id, user.id)
But I want to know if I can get using the User model. For example:
`User.game`
Thank you!
Define has_many :games with foreign_key option.
class User
has_many :white_games, class_name: 'Game', foreign_key: 'player_white_id'
has_many :black_games, class_name: 'Game', foreign_key: 'player_black_id'
# If you want all game
def games
Game.where('player_white_id = ? OR player_black_id = ?', self.id, self.id)
end
end
User.games will throw an error because there is not a method on the User class named games. There is a method called games on an instance of the User class though. Try creating a User with Games first then try User.first.games.

Accessing a join-model attribute stored in a join table created with #create_join_table

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}])

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.

Can't get a non-mass-assignment value to set or save

frustrating simple problem here. I'm using Rails 3.1 and have the following class:
class Job < ActiveRecord::Base
attr_accessor :active
attr_accessible :roleTagline, :projectTagline, :projectStartDate, :projectDuration, :postedStartDate,
:postedEndDate, :skillsRequired, :skillsPro, :experiencedRequired, :description, :active
scope :is_active, :conditions => {:active => 1}
validates :roleTagline, :presence => true,
:length => { :minimum => 5 }
validates :projectTagline, :presence => true,
:length => { :minimum => 5 }
belongs_to :job_provider
# def active=(act)
# #active = act
# end
end
In my controller, I'm trying to create a Job using mass-assignment (one of the ActiveRecord build helpers), then afterwards set the "active" attribute to 1 and then save the Job. Here's the controller code:
def create
#job_provider = current_user.job_provider
#job = #job_provider.jobs.build(params[:job])
#job.active= 1 # debug logging #job.active here gives an empty string
if #job.save # etc.
I've tried all combinations of removing attr_accessor and writing my own setter instead, but whatever I do I can't seem to find the correct combination to get the attribute to stay on the model. It's not active record I don't think because even before the #job.save the attribute is gone (from using debug logging). I've googled around but can't figure out what I'm doing wrong. Can anyone help please?
Edit: schema.rb from rake:
create_table "jobs", :force => true do |t|
t.string "roleTagline"
t.string "projectTagline"
t.date "projectStartDate"
t.integer "projectDuration"
t.date "postedStartDate"
t.date "postedEndDate"
t.string "skillsRequired"
t.string "skillsPro"
t.string "experiencedRequired"
t.string "description"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "active"
t.integer "job_provider_id"
end
Another edit:
Ok after even more Googling I finally found the answer to this here:
How can I override the attribute assignment in an active record object?
If you're modifying an ActiveRecord attribute and not a class instance, you need to do:
self[:fieldname] = value
Remove attr_accessor :active from the model. It is causing the value to be saved in an instance variable rather than to the database via the attributes hash. You don't have to write an accessor, because ActiveRecord does that automatically for you, based on the active column.
It's showing up blank in the debug log because it is initialized to nil. If you remove the attr_accessor line and change the active column in the database to NOT NULL DEFAULT 0, it will be initialized to 0. i.e. :null => false, :default => 0 in an ActiveRecord migration or schema.rb.
I think you didn't add the job_provider_id to the att_accessible. Try the following
attr_accessible :roleTagline, :projectTagline, :projectStartDate, :projectDuration, :postedStartDate, :postedEndDate, :skillsRequired, :skillsPro, :experiencedRequired, :description, :active, :job_provider_id

Resources