Attributes passed to .build() dont show up in the query - activerecord

Hope your all enjoying your hollydays.
Ive run into a pretty funny problem when trying to insert rows into a really really simple database table.
The basic idea is pretty simple. The user selects one/multiple users in a multiselect which are supposed to be added to a group.
This piece of code will insert a row into the user_group_relationships table, but the users id always
#group = Group.find(params[:group_id])
params[:newMember][:users].each do |uid|
# For debugging purposes.
puts 'Uid:'+uid
#rel = #group.user_group_relationships.build( :user_id => uid.to_i )
#rel.save
end
The user id always gets inserted as null even though it is clearly there. You can see the uid in this example is 5, so it should work.
Uid:5
...
SQL (0.3ms) INSERT INTO
"user_group_relationships"
("created_at", "group_id",
"updated_at", "user_id") VALUES
('2010-12-27 14:03:24.331303', 2,
'2010-12-27 14:03:24.331303', NULL)
Any ideas why this fails?

Looks like user_id is not attr_accessible in the UserGroupRelationship model.
You might also want to check this it may be relevant.

I think #zabba's answer is probably the one you need to look for but i would suggest a couple of extra things here.
Your "Group" and "User" models are connected to each other thru the "UserGroup" model it seems. You would have relationship
class Group < ActiveRecord::Base
has_many :user_group_relationships
has_many :users, :through => :user_group_relationships
end
class UserGroupRelationship < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
In your controller
# Find the group
#group = Group.find(params[:group_id])
# For each user id, find the user and add the user_group_relationship
params[:newMember][:users].each{|u| #group.users << User.find(u) }
Read up Rails Documentation on associations and the methods generated automatically for you when associations are defined. More often than not, working with association is easier than you might think! I discover new APIs in Rails constantly! :) Good Luck!

Related

Activerecord/Datamapper - Have one child belong to many parents

How would you set up an activerecord/datamapper association for the following scenario:
A user creates a "bookshelf" which has many books(a book object just has an isbn that is used to query an api, and has_many review objects associated with it). Let's say Jack creates a "bookshelf" with a book object. Then, lets say that Jill creates a "bookshelf" with the same book object(it has the same id and the same reviews). The book object has the following code as of now:
class Book < ActiveRecord::Base
has_many :reviews
end
Then, when you view the page for a book (you click the link to it from the "bookshelf" created by Jack) you should see the same book object when you clicked the link to it from Jill's "bookshelf" (e.g. both "bookshelves" have a link to /books/23 because they have the same book object).
I have not been able to figure this out with the has_many association because that requires me to make a new book each time a user adds a book to their "bookshelf." I have trouble understanding the has_and_belongs_to_many relationship, is that what should be used here? I was not able to find any similar questions on SO, so any help is greatly appreciated.
I am using Rails 4 with Ruby 2.1.
Here is a drawing of what I would like to accomplish:
Drawing
Yes, you would have to define many-to-many relationship between a Bookshelf and a Book. There are two ways to achieve this in Rails:
Option 1) Use has_and_belongs_to_many
See guide
According to official documentation has_and_belongs_to_many association:
Specifies a many-to-many relationship with another class. This associates two classes via an intermediate join table. Unless the join table is explicitly specified as an option, it is guessed using the lexical order of the class names. So a join between Developer and Project will give the default join table name of “developers_projects” because “D” precedes “P” alphabetically.
So, your classes should look like this:
class Bookshelf < ActiveRecord::Base
has_and_belongs_to_many :books
end
class Book < ActiveRecord::Base
has_and_belongs_to_many :bookshelves
has_many :reviews
end
Add a join table generation to your migrations:
class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
def change
create_table :books_bookshelves, id: false do |t|
t.belongs_to :book, index: true
t.belongs_to :bookshelf, index: true
end
end
end
This will create a books_bookshelves table in your database. The table will have no primary key. There would be two foreign keys to your models Book and Bookshelf.
So, if you call self.books in the context of an user's bookshelf, you will get a list of books in the bookshelf. Vice versa, calling self.bookshelves in the context of a book will return a set of bookshelves the book belongs to.
The problem with this approach is that every time you add a new book to the bookshelf a new record is created in the database. If you are okay with that, there is no easier option than using has_and_belongs_to_many association. Otherwise, I recommend you to go with the Option #2.
Option 2) Use has_many :through
Another option is to use has_many, :through association (see guide). You would have to define one more model to do that, but it might come handy in some use cases (see below for an example).
Your classes should look like this:
class Bookshelf < ActiveRecord::Base
has_many :books, through: :books_bookshelves
has_many :books_bookshelves
end
class Book < ActiveRecord::Base
has_many :bookshelves, through: :books_bookshelves
has_many :books_bookshelves
has_many :reviews
end
class BooksBookshelf < ActiveRecord::Base
belongs_to :book
belongs_to :bookshelf
end
Probably the best thing about using has_many :through association is that it allows you to add custom columns to the join table (e.g. add column count to keep track how many books of the same type are there in the bookshelf).
The migration would look pretty much the same as the one we used in Option 1, except for the fact we are adding an unique constraint on the foreign keys (please note that adding the constraint is optional):
class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
def change
create_table :books_bookshelves, id: false do |t|
t.belongs_to :book, index: true
t.belongs_to :bookshelf, index: true
# add your custom columns here
end
add_index :books_bookshelves, [:book_id, :bookshelf_id], unique: true # to make sure you won't create duplicate records
end
end
By going with this approach, adding a new would be a bit more complicated as you would have to make sure you are not inserting duplicate records in the join table. (However, you may remove the unique constraint from the migration, to achieve exactly the same kind of behavior as you would get with has_and_belongs_to_many.)

Access attribute in Active Record 'through' association object

I have two classes, User and Product in a 'many-to-many through' association, using the class Prole (for product role).
class User < ActiveRecord::Base
has_many :proles
has_many :products, through: :proles
end
class Product < ActiveRecord::Base
has_many :proles
has_many :users, through: :proles
end
class Prole < ActiveRecord::Base
# has an attribute called 'role'
end
prole has an attribute called role which I'd like to use to qualify the user-product association.
The association works fine, but I can't figure out how to access the role attribute after creating the association. For example, if I do:
user.products << product
how can I access the attribute in the prole object just created?
I guess I could iterate through the prole objects and find the correct one, but I'm hoping there's a cleaner way.
Is this possible? Any hints?
TIA.
I was hoping for something a little more direct, but here's a
POSSIBLE ANSWER:
prole = Prole.find_by user_id: user.id, product_id: product.id
or even better
prole = user.proles.where("product_id = #{product.id}")
After some testing, it looks like the easiest way to grab specifically the Prole object that was just created is by querying by the two foreign keys directly against the Prole model, as suggested in your possible answer.
Prole.find_by(user_id: user.id, product_id: product.id)
If you want it as an association on the user object, you could use the includes approach to do eager loading, but it will still load every prole for the user in question
# specifying proles: {product_id: product.id} in the where clause here
# only limits users retrieved, not proles
user = User.includes(:proles).where(id: user.id)
# eager-loaded prole array
user.proles.find { |prole| prole.product_id == product.id }
See this answer for more info on that. But it looks like your possible answer is the cleanest way.

Rails join on 2 tables

I have 3 tables which are
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
Now I want to retrieve all the comments of a particular user.
I have tried it using find_by_sql like
Comment.find_by_sql "SELECT c.* FROM comments c, posts p, users u WHERE c.post_id = p.id and p.user_id = u.id and u.id=6"
This works fine. But I want few details from "posts" table along with comments.
Does anyone have idea how to do that?
Corrected answer..
Now that I've properly understood the scenario, a portion of my previous answer still makes sense with one addition:
Alter the User model like so:
class User < ActiveRecord::Base
has_many :posts
has_many :comments, through: :posts
end
This type of relationship will perform a join between comments a posts, where the user_id of posts is the current user. Quite literally, like you said in the comments, "the connection between User and Comment [is] through Post".
Similar to what I said before, the center of the universe in this scenario is the user object. With this addition, to grab all the user's comments would simply be:
user.comments
If you look at the log, the SQL output is:
SELECT `comments`.* FROM `comments` INNER JOIN `posts` ON `comments`.`post_id` = `posts`.`id` WHERE `posts`.`user_id` = 1
Which is similar to your original query.
Now that you retrieved the comments for this user, you can simply access the Post model data through the belongs_to :post relationship, as normal.
In the end, unless you have some special reason for trying to do everything in pure SQL, this approach is more readable, maintainable, and the "Rails way". Plus it saves yourself a lot of hassle and typing.
More information on has_many ... through.

Join type in ActiveRecord has_one Relationship

Just getting started with ActiveRecord (in a sinatra app). Trying to port existing queries to AR but getting a little stuck.
if i have a has_one relation for users and profiles (using legacy tables unfortunately)
class User < ActiveRecord::Base
self.table_name = "systemUsers"
self.primary_key = "user_id"
has_one :profile, class_name: 'Profile', foreign_key: 'profile_user_id'
end
class Profile < ActiveRecord::Base
self.table_name = "systemUserProfiles"
self.primary_key = "profile_id"
belongs_to :user, class_name: "User", foreign_key: 'user_id'
end
if i want to query all users with profile using an inner join then get the user_age field from the profiles using one query can i do it?
for example
(just added .first to reduce code but would be looping through all users with profiles)
user = User.all(:joins => :profile).first
user.profile.user_age
gives me the correct data and uses a INNER join for the first query but then issues a second query to get the profile data
it also gives a depreciated warning and suggests i use load, which i tried but won't use an inner join.
similar case with
user = User.joins(:profile).first
user.profile.user_age
i get an INNER join but a query for each user row.
I have tried includes
user = User.includes(:profile).first
user.profile.user_age
This lazy loads the profile and would reduce the number of queries in the loop, however i think it would pull users without a profile too
I also tried with a reference
user = User.includes(:profile).references(:profile).first
user.profile.user_age
This gives me the correct data and reduces the queries to 1 but uses a LEFT JOIN
I probably have not quite grasped it and am trying to achieve something thats not do-able, i figured i might either need to use includes and check for nil profiles inside the loop or use joins and accept the additional query for each row.
Thought i'd check incase i was missing something obvious.
Cheers
Pat.
Profile should always have one user. So, I would do Profile.first.user_age for the first user profile. But going by the user approach like you did,
User.find { |u| u.profile }.profile.user_age
User.find { |u| u.profile } returns the first user with true value.
To query all the user profiles and get their user_ages. Assuming all profiles has user_id and that should be the case.
Profile.pluck(:user_age)
This checks the presence of user_id if you save profiles without user id. This where.not is a new feature in Activerecord, check your version.
Profile.where.not(user_id: nil).pluck(:user_age)

Rails 3.1 - Binding HABTM from other controller

I have:
class Person < ActiveRecord::Base
has_many :people_phones
has_many :phones, :through => :people_phones
end
I also have:
class Request < ActiveRecord::Base
belongs_to :person
belongs_to :phone
end
Now when someone call with a request I open "requests#new" form, fill in person_id and phone_number and other details and submits them to "requests#create" controller#action.
In the "requests#create", I can do:
#phone = Phone.find_or_create_by_phone_number(params[:phone][:phone_number])
But how can I bind Person with that Phone from this Requests controller?
I mean create a record in people_phones table (if it doesn't exists)?
User.find(person_id).phones << #phone
I don't really know how your app works, but you see the idea.
If you have a request, and you want to "validate" it, you would do
request.person.phones << request.phone
Interesting stuff to know, it's kind of related (I'll try to find where I found that, it was a long time ago)
Steps required for the object to be saved to database:
New
Blog.new(…).save
user.blogs << Blog.new(…)
user.blogs.new(…).save – do not use, no practical use case
Build
Blog.build – not possible
user.blogs.build(…), user.save – both are required to save to DB
Create
Blog.create(…)
user.blogs.create(…)

Resources