I have serialized column of Post model
serialize :user_ids
after save I can see in my console:
> p = Post.last
=> #<Post id: 30, title: "almost done2", created_at: "2017-05-08 15:09:40", updated_at: "2017-05-08 15:09:40", attachment: "LOGO_white.jpg", user_ids: [1, 2]>
I have permitted params :user_ids at my controller
def post_params
params.require(:post).permit(:title, :attachment, :user_ids)
end
All I need is to store #user_ids and copy to other model as atrribute,
but can't get instance like #user_ids = #Post.last.user_ids
Get error
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: users.post_id: SELECT "users".id FROM "users" WHERE "users"."post_id" = ?
Thanks for help!
I think you've chosen really wrong name for your serialized attribute, unfortunately!
See, Rails is trying to be helpful and relation_ids method is allowing you to call ids of all related object. Consider those two models
class User
belongs_to :post
end
class Post
has_many :users
end
You can call something like
Post.last.user_ids
Which will then try to find all users that belong to this post and give you back their ids.
Look at query that you get:
SELECT "users".id FROM "users" WHERE "users"."post_id" = ?
This is exactly what Rails is trying to do. Select USERS that belong to the post and give you their ids. However User does not have post_id column so this fails.
You're better off changing the name of your serialized column to something that won't confuse Rails. However, if you want to keep your column, you can override the method by putting this on the Post model
def user_ids
self[:user_ids]
end
This is non-ideal however, but will leave it up for you to decide what to do. My preference would be column rename, really. Will save you a lot of headeache
Related
I want to find an associated models record by multiple params passed in a form to create a user_product which associates with the product model.
Create method in controller:
#user_product = UserProduct.new(user_product_params)
#product = Product.where(style_id: #user_product.style_id).where(size: #user_product.size).where(color: #user_product.color).where(country: #user_product.country)
#user_product.product_id = #product.id
Models:
Product:
belongs_to :category, foreign_key: :category_id, class_name: "Category"
belongs_to :style, foreign_key: :style_id, class_name: "Style"
belongs_to :user_products
UserProduct:
has_one :product
has_many :orders
The form passes:
size, color, style_id, category_id
I keep getting the error:
undefined method `id' for #<Product::ActiveRecord_Relation:x> Did you mean? ids
Everything is being passed but the product_id though.
How can I find the product_id by using the multiple params passed in the form?
where returns an array records but you are looking for a single record only. You can use find_by which returns the first record matching the conditions (or nil, if none was found):
#product = Product.find_by(
style_id: #user_product.style_id,
size: #user_product.size,
color: #user_product.color,
country: #user_product.country
)
if #product
#user_product.product_id = #product.id
else
# add an error if the product is not found,
# or do something else
end
The problem you have is quite clear from the error message. As undefined method 'id' for #<Product::ActiveRecord_Relation:x> Did you mean? ids denotes, you don't have one product with an id but a relation of - possibly - multiple products. So you might want to try: #product.first.id instead.
As you are using where query it will return ActiveRecord::Relation , which is in form of array even if it returns single record. so your #product variable has result in Array form, if you are sure that you will only get one record from this query you should use .take method , but i suggest you use .find_by method for finding the record.
where clause return ActiveRecord object(s) in a ActiveRecord_Relation that is a kind of Array even there is a single record. Try this
#user_product = UserProduct.new(user_product_params)
#products = Product.where(style_id: #user_product.style_id, size: #user_product.size, color: #user_product.color, country: #user_product.country)
#user_product.product_id = #products.first.id
Hope you find this helpful !!!
where condition give you the result in array.
Try this
#product.first.id
How do I convert to JSON and back and keep the relationships? It thinks they don't exist when I un-parcel the object!
irb(main):106:0* p = Post.last
=> #<Post ...
irb(main):107:0> p.tags
=> #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 41, ...
irb(main):109:0* p.tags.count
=> 2 #### !!!!!!!!!!!!
irb(main):110:0> json = p.to_json
=> "{\"id\":113,\"title\":... }"
irb(main):111:0> p2 = Post.new( JSON.parse(json) )
=> #<Post id: 113, title: ...
irb(main):112:0> p2.tags
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):113:0> p2.tags.count
=> 0 #### !!!!!!!!!!!!
Here is the model
class Post < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :tags, :through => :taggings
What someone suggested, but doesn't work
irb(main):206:0* Post.new.from_json p.to_json(include: :tags)
ActiveRecord::AssociationTypeMismatch: Tag(#60747984) expected, got Hash(#15487524)
I simulated the exact same scenario like yours and found out:
Whenever a model(Post) has a has_many through association then upon creating an instance of that Model i.e., Post passing a Hash for eg: Post.new( JSON.parse(json) ) or Post.new(id: 113) seems like Rails treats them differently although they are pointing to the same record.
I ran the following commands in the sequence as given below:
p = Post.last
p.tags
p.tags.count
json = p.to_json
p2 = Post.new( JSON.parse(json) )
p2.tags
p2.tags.count ## Gives incorrect count
p3 = Post.find(JSON.parse(json)["id"]) ### See notes below
p3.tags
p3.tags.count ## Gives the correct count
Instead of creating a new instance of Post using Hash directly, I fetched the record from database using the id obtained from deserializing json. In this case, the instance p3 and instance p2 refer to the same Post but Rails is interpreting them differently.
Disclaimer: This is not, in any way, an ideal solution (and I would call it down-right cheesy), but its about the only thing I've been able to come up with for your scenario.
What Kirti Thorat said is correct; when you have a dependent object, Rails expects the association in the hash to be of that specific class (in your case, a Tag object). Hence the error you're getting: Tag expected...got Hash.
Here comes the cheesy part: One way to properly deserialize a complex object is to leverage the accepts_nested_attributes_for method. By using this method, you'll allow your Post class to properly deserialize the dependent Tag key-value pairs to proper Tag objects. Start with this:
class Post < ActiveRecord::Base
accepts_nested_attributes_for :tags
# rest of class
end
Since accepts_nested_attributes_for searches for a key with the word _attributes for the given association, you'll have to alter the JSON when it is rendered to accommodate this by overriding the as_json method in your Post class, like so:
def as_json(options={})
json_hash = super.as_json(options)
unless json_hash["tags"].nil?
json_hash["tags_attributes"] = json_hash["tags"] # Renaming the key
json_hash.delete("tags") # remove the now unnecessary "tags" key
end
json_hash # don't forget to return this at the end
end
Side note: There are lots of json building gems such as acts_as_api that will allow you to remove this as_json overriding business
So now your rendered JSON has all the Post attributes, plus an array of tag attribute key-value pairs under the key tags_attributes.
Technically speaking, if you were to deserialize this rendered JSON in the manner suggested by Kirti, it would work and you would get back a properly populated active record object. However, unfortunately, the presence of the id attributes in both the parent Post object, and the dependent tag objects means that active record will fire off at least one SQL query. It will do a quick lookup for the tags to determine if anything needs to be added or deleted, as per the specifications of the has_many relationship (specifically, the collection=objects part).
Since you said you'd like to avoid hitting the database, the only solution I've been able to find is to render to JSON in the same way leesungchul suggested, but specifically excluding the id fields:
p_json = p.to_json(except: [:id], include: {tags: {except: :id}})
If you then do:
p2 = Post.new(JSON.parse(p_json))
You should get back a fully rendered Post object without any DB calls.
This, of course, assumes you don't need those id fields. In the event you do...frankly I'm not certain of a better solution other than to rename the id fields in the as_json method.
Also note: With this method, because of the lack of id fields, you won't be able to use p2.tags.count; it will return zero. You'll have to use .length instead.
You can try
p2.as_json( :include => :tags )
When you call
p2.tags
you get correct tags but p2 is not saved in the database yet. This seems the reason for
p2.tags.count
giving a 0 all the time.
If you actually do something like:
p2.id = Post.maximum(:id) + 1
p2.tags #Edit: This needs to be done to fetch the tags mapped to p from the database.
p2.save
p2.tags.count
You get the correct count
Rails 3.2.11
MySql2 Gem
Hi, does anybody have an idea why my find method is returning an activerecord of the wrong type?
Here's the model:
class NameList < ActiveRecord::Base
attr_accessible :name, :selected, :type
self.table_name='name_lists'
end
Here's the console output:
>> k=NameList.find(28)
NameList Load (0.0ms) SELECT `name_lists`.* FROM `name_lists` WHERE `name_lists`.`id` = 28 LIMIT 1
#<Neighborhood id: 28, name: "Bayview">
>> k.class
Neighborhood(id: integer, city_id: integer, name: string, street_count: integer, relative_value: float, home_count: integer, min_lot_size: integer, created_at: datetime, updated_at: datetime)
Notice that I am calling NameList.find, but what I get back is Neighborhood object. Oddly, the sql seems right -- its querying the NameList table.
There's nothing particularly special about the Neighborhood object: Here's the model for that:
class Neighborhood < ActiveRecord::Base
belongs_to :city
has_many :streets
attr_accessible :name, :relative_value, :street_count
def self.make(name, relative_value, min_lot_size, street_count, home_count)
n=Neighborhood.new
n.name = name
end
end
When I try to save it though -- it uses the definition of schema of Neighborhood and tries to update the wrong table.
> k.name = "Foo"
"Foo"
>> k.save
(1.0ms) BEGIN
(0.0ms) UPDATE `neighborhoods` SET `name` = 'Foo', `updated_at` = '2013-03-14 17:40:46' WHERE `neighborhoods`.`id` = 28
(0.0ms) COMMIT
true
Any ideas?
You have stumbled upon Rails Single Table Inheritance (STI), which you immediately get if you add a 'type' column to your table. Essentially the object is instantiated from the class whose name matches the value of the type column in that record.
If you want to learn about STI, read on here, look for the Single Table Inheritance section. In your case, I bet you don't really want that behavior, so your solution is to rename the column to something like kind, category, or whatever makes sense to you
If the column name type is desired however, you can turn off the Single Table Inheritance on a per model basis. See my answer on this post
artists have many activities (basically a cache of interactions between users):
class Activity < ActiveRecord::Base
belongs_to :receiver, :class_name => 'Artist', :foreign_key => :receiver_id #owns the stuff done "TO" him
belongs_to :link, :polymorphic => true
belongs_to :creator, :class_name => 'Artist', :foreign_key => :creator_id #person who initiated the activity
end
For example:
Activity.create(:receiver_id => author_id, :creator_id => artist_id, :link_id => id, :link_type => 'ArtRating')
I want to create an activity stream page for each artist, consisting of a list of different types of events, ArtRatings (likes, dislikes), Favoriting, Following etc.
The controller looks like this:
class ActivityStreamController < ApplicationController
def index
#activities = #artist.activities.includes([:link,:creator,:receiver]).order("id DESC").limit(30)
end
end
The db call correctly eagerly loads the polymorphic link objects:
SELECT "activities".* FROM "activities" WHERE (("activities"."receiver_id" = 6 OR "activities"."creator_id" = 6)) ORDER BY id DESC LIMIT 30
ArtRating Load (0.5ms) SELECT "art_ratings".* FROM "art_ratings" WHERE "art_ratings"."id" IN (137, 136, 133, 130, 126, 125, 114, 104, 103, 95, 85, 80, 73, 64)
SELECT "follows".* FROM "follows" WHERE "follows"."id" IN (14, 10)
SELECT "favorites".* FROM "favorites" WHERE "favorites"."id" IN (25, 16, 14)
But when I display each ArtRating, I also need to reference the post title, which belongs to a post. In the view, if I do:
activity.link.post
It does a separate DB call for each art_rating's post. Is there a way to eagerly load the post as well?
UPDATE TO THE QUESTION:
If there is no way to achieve eager loading of posts using 'includes' syntax, is there a way to manually do the eager loading query myself and inject it into the #activities object?
I see in the DB log:
SELECT "art_ratings".* FROM "art_ratings" WHERE "art_ratings"."id" IN (137, 136, 133, 130, 126, 125, 114, 104, 103, 95, 85, 80, 73, 64)
Is there a way I can access this list of ids from the #activities object? If so, I could do 2 additional queries, 1 to get the art_ratings.post_id(s) in that list, and another to SELECT all posts IN those list of post_ids. Then somehow inject the 'post' results back into #activities so that it's available as activity.link.post when I iterate through the collection. Possible?
TL;DR my solution makes artist.created_activities.includes(:link) eager load everything you want
Here's my first attempt at it: https://github.com/JohnAmican/music
A few notes:
I'm relying on default_scope, so this isn't optimal.
It looks like you're using STI. My solution doesn't. That means you can't simply call activities on an artist; you have to reference created_activities or received_activities. There might be a way around this. I'll update if I find anything.
I changed some names around because it was confusing to me otherwise.
If you go into console and do created_activities.includes(:link), the appropriate stuff gets eager-loaded:
irb(main):018:0> artist.created_activities.includes(:link)
Activity Load (0.2ms) SELECT "activities".* FROM "activities" WHERE "activities"."creator_id" = ? [["creator_id", 1]]
Rating Load (0.3ms) SELECT "ratings".* FROM "ratings" WHERE "ratings"."id" IN (1)
RatingExplanation Load (0.3ms) SELECT "rating_explanations".* FROM "rating_explanations" WHERE "rating_explanations"."rating_id" IN (1)
Following Load (0.3ms) SELECT "followings".* FROM "followings" WHERE "followings"."id" IN (1)
Favorite Load (0.2ms) SELECT "favorites".* FROM "favorites" WHERE "favorites"."id" IN (1)
=> #<ActiveRecord::Relation [#<Activity id: 1, receiver_id: 2, creator_id: 1, link_id: 1, link_type: "Rating", created_at: "2013-10-31 02:36:27", updated_at: "2013-10-31 02:36:27">, #<Activity id: 2, receiver_id: 2, creator_id: 1, link_id: 1, link_type: "Following", created_at: "2013-10-31 02:36:41", updated_at: "2013-10-31 02:36:41">, #<Activity id: 3, receiver_id: 2, creator_id: 1, link_id: 1, link_type: "Favorite", created_at: "2013-10-31 02:37:04", updated_at: "2013-10-31 02:37:04">]>
At the very least, this proves that Rails has the ability to do this. Circumventing default_scope seems like an issue with telling Rails what you want to do rather than a technical limitation.
UPDATE:
Turns out that when you pass a scope block to an association and call that association, self within that block refers to the relation. So, you can reflect on it and act appropriately:
class Activity < ActiveRecord::Base
belongs_to :creator, class_name: 'Artist', foreign_key: :creator_id, inverse_of: :created_activities
belongs_to :receiver, class_name: 'Artist', foreign_key: :receiver_id, inverse_of: :received_activities
belongs_to :link, -> { self.reflections[:activity].active_record == Rating ? includes(:rating_explanation) : scoped }, polymorphic: true
end
I updated my code to reflect (haha) this.
This can be cleaned up. For example, maybe you don't always want to eager load rating_explanations when accessing activity links. There are a number of ways to solve that. I could post one if you'd like.
But, I think the most important thing that this shows is that within the association's scope block, you have access to the ActiveRecord::Relation being built. This will allow you to do things conditionally to it.
Try this:
#artist.activities.includes([{:link => :post},:creator,:receiver])...
See the Rails docs for more.
If I get this right, you not only want to load a polymorphic association's association, but a polymorphic association's polymorphic association.
This basically means joining one defined table with another defined table through a bunch of undefined tables that come from some fields in the database.
Since both the activity and the post are joined through a polymorphic object they both have a something_id and something_type column.
Now I think active record doesn't let you do this out of the box but basically you want something like:
class Activity
has_one :post, :primary_key => [:link_id, :link_type], :foreign_key => [:postable_id, :postable_type]
end
(assuming your polymorphic association on Post is belongs_to :postable)
That would then give you a query sort-of direct association between Post and Activity that is a very weird take on habtm with a 'polymorphic join table' (I just made that term up). Because both share the same polymorphicly associated object, they can be connected. Sort of like a friends-of-my-friends thing.
CAVEAT: Like I said, as far as I know, AR doesn't let you do this out of the box (though it would be awesome) but there are some gems that give you composite foreign and/or primary keys. Maybe you can find one to help you solve your problem in this way.
Given your update to the question (that this isn't feasible using AR's include), you could try the following to get the associated Posts with a single extra query:
fetch the Activities, then build an Array of their linked post_ids.
#activities = #artist.activities.includes([:link,:creator,:receiver])
activity_post_ids = #activities.map{|a| a.link.post_id}.compact
then load the posts in one query and store them in a Hash, indexed by their id
activity_posts = Hash[Post.where(id: activity_post_ids).map{|p| [p.id, p]}]
#=> {1 => #<Post id:1>, 3 => #<Post id:3>, ... }
finally loop over #activities and set the post attribute of each associated link
#activities.each{|a| a.link.post = activity_posts[a.link.post_id]}
This simple ActiveRecord should work:
#artist.activities.includes([:link => :post,:creator,:receiver]).order("id DESC").limit(30)
If not, if you are getting an error like "Association named 'post' was not found; perhaps you misspelled it?", then you have a model problem, because at least one of your link associations doesn't have the post association.
Polymorphic associations are intended to be used with a common interface. If you ask for the post for any of the polymorphic association, then ALL of your associations should implemented also that association (the post one).
Check that all of your models used in the polymorphic association implement the post association.
I have three models:
class Book < ActiveRecord::Base
has_many :collections
has_many :users, :through => :collections
end
class User < ActiveRecord::Base
has_many :collections
has_many :books, :through => :collections
end
class Collection < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
I'm trying to display a list of the books and have a link to either add or remove from the user's collection. I can't quite figure out the best syntax to do this.
For example, if I do the following:
Controller
class BooksController < ApplicationController
def index
#books = Book.all
end
end
View
...
<% if book.users.include?(current_user) %>
...
or obviously the inverse...
...
<% if current_user.books.include?(book) %>
...
Then queries are sent for each book to check on that include? which is wasteful. I was thinking of adding the users or collections to the :include on the Book.all, but I'm not sure this is the best way. Effectively all I need is the book object and just a boolean column of whether or not the current user has the book in their collection, but I'm not sure how to forumlate the query in order to do that.
Thanks in advance for your help.
-Damien
I have created a gem(select_extra_columns) for returning join/calculated/aggregate columns in a ActiveRecord finders. Using this gem, you will be able to get the book details and the flag indicating if the current user has the book in one query.
In your User model register the select_extra_columns feature.
class Book < ActiveRecord::Base
select_extra_columns
has_many :collections
has_many :users, :through => :collections
end
Now in your controller add this line:
#books = Book.all(
:select => "books.*, IF(collections.id, 1, 0) AS belongs_to_user",
:extra_columns => {:belongs_to_user => :boolean},
:joins => "LEFT OUTER JOIN collections
ON book.id = collections.book_id AND
collections.user_id = #{current_user.id}"
)
Now in your view you can do the following.
book.belongs_to_user?
You're going to to want 2 SQL queries, and O(1) based lookups (probably irrelevant, but it's the principle) to check if they have the book.
The initial calls.
#books = Book.all
#user = User.find(params[:id], :include => :collections)
Next, you're going to want to write the books the user has into a hash for constant time lookup (if people won't ever have many books, just doing an array.include? is fine).
#user_has_books = Hash.new
#user.collections.each{|c|#user_has_books[c.book_id] = true}
And on the display end:
#books.each do |book|
has_book = #user_has_books.has_key?(book.id)
end
I'd err away from caching the book_ids on the user object, simply because going this route can have some funny and unexpected consequences if you ever start serializing your user objects for whatever reason (i.e. memcached or a queue).
Edit: Loading intermediary collection instead of double loading books.
Essentially you need to make one call to get the book information and the Boolean flag indicating if the current user has the book. ActiveRecord finders doesn't allow you to return the join results from another table. We work around this problem by doing a trick.
In your Book model add this method.
def self.extended_book
self.columns # load the column definition
#extended_user ||= self.clone.tap do |klass|
klass.columns << (klass.columns_hash["belongs_to_user"] =
ActiveRecord::ConnectionAdapters::Column.new(
"belongs_to_user", false, "boolean"))
end # add a dummy column to the cloned class
end
In your controller use the following code:
#books = Book.extended_book.all(
:select => "books.*, IF(collections.id, 1, 0) AS belongs_to_user",
:joins => "LEFT OUTER JOIN collections
ON book.id = collections.book_id AND
collections.user_id = #{current_user.id}"
)
Now in your view you can do the following.
book.belongs_to_user?
Explanation:
In the extended_book method you are creating a copy of Book class and adding a dummy column belongs_to_user to the hash. During the query extra join column is not rejected as it exists in the columns_hash. You should use the extended_book only for querying.
If you use it for CRUD operations DB will throw error.
I would first create an instance method in the User model that 'caches' the all the Book ID's in his collection:
def book_ids
#book_ids ||= self.books.all(:select => "id").map(&:id)
end
This will only execute the SQL query once per controller request. Then create another instance method on the User model that takes a book_id as a parameter and checks to see if its included in his book collection.
def has_book?(book_id)
book_ids.include?(book_id)
end
Then while you iterate through the books:
<% if current_user.has_book?(book.id) %>
Only 2 SQL queries for that controller request :)
Use exists? on the association as it is direct SQL call. The association array is NOT loaded to perform these checks.
books.users.exists?(current_user)
This is the SQL executed by Rails.
SELECT `users`.id FROM `users`
INNER JOIN `collections` ON `users`.id = `collections`.user_id
WHERE (`users`.`id` = 2) AND ((`collections`.book_id = 1)) LIMIT 1
In the above SQL current_user id = 2 and book id is 1
current_user.books.exists?(book)
This is the SQL executed by Rails.
SELECT `books`.id FROM `books`
INNER JOIN `collections` ON `books`.id = `collections`.book_id
WHERE (`books`.`id` = 3) AND ((`collections`.user_id = 4)) LIMIT 1
In the above SQL current_user id = 4 and book id is 3
For more details, refer to the documentation of the exists? method in a :has_many association.
Edit: I have included additional information to validate my answer.