Left outer join a nested select in Rails - ruby-on-rails

I'm trying to set up a classic 'like' model for Posts on a blog, where users can create one Like for any Post. I have the following models:
class Post < ApplicationRecord
belongs_to :user
has_many :likes
end
class User < ApplicationRecord
has_many :posts
has_many :likes
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :post, counter_cache: true
end
In my controller I monitor the currently logged in user, current_user.
I would like to add a column to my Posts model that indicates whether or not current_user has liked each Post.
I tried adding a method to the Posts model that looks for likes:
class Post < ApplicationRecord
belongs_to :user
has_many :likes
def user_liked
!likes.empty?
end
end
And using includes in the controller method.
#posts = Post.includes(likes: { user: current_user }).where(safe_params).order(order)
render json: #posts
However I get the following error:
ArgumentError (#<User id: 1, username: "pete", ... > was not recognized for preload):
app/controllers/posts_controller.rb:51:in `index'
I'm using Rails API 5.0.
Update:
To clarify, I'm looking for the Rails equivalent of this SQL statement:
SELECT *
FROM Posts
LEFT OUTER JOIN
(SELECT *
FROM Likes
WHERE Likes.user_id = current_user.id) AS MyLikes
ON Posts.id = MyLikes.post_id

The problem is includes takes the name of associations as parameters (like :user, :likes, :posts) as parameter and does not include any instances(In your case current_user)
You could try the following instead.
#posts = Post.includes(likes: :user).where(safe_params).order(order)
If you want to check if posts belong to current_user or not (in haml view for example)
- #posts.each do |post|
- if post.likes && post.likes.any? { |like| like.user_id == current_user.id }
%span You have commented on this post

Related

Can Rails have dynamic relationships in has_many?

I have a Lesson model which has many Completions like this:
class Lesson < ActiveRecord::Base
has_many :completions, as: :completable
belongs_to :course
end
And each Completion belongs to a User as well:
class Completion < ActiveRecord::Base
belongs_to :user
belongs_to :completable, polymorphic: true
end
From my application perspective I'm only interested in the amount of completions for a certain lesson, so I've included a counter cache. In regard to the individual Completions, I'm only interested if the Lesson is completed by the current user (I'm using Devise).
Is there some way to create a dynamic has_one relationship of some kind, that uses the information from the current_user to query the Completion table?
for instance:
has_one :completion do
def from_user current_user
Completion.where(completable: self, user: current_user)
end
end
Although this could work, I'm also having a polymorphic relationship. Rails is complaining that there's no foreign key called lesson_id. When I add a foreign_key: symbol, the do-end block stops working.
Any ideas?
Why not passing both block and options to has_many?
class Lesson < ActiveRecord::Base
has_many :completions, as: :completable do
def from_user user
if loaded?
find {|c| c.user_id = user.id}
else
find(user_id: user.id)
end
end
end
belongs_to :course
end
#lesson = Lesson.last
# Association not loaded - executing sql query
#lesson.completions.from_user(current_user)
#lesson.completions
# Association loaded - no sql query
#lesson.completions.from_user(current_user)
NOTE: You cannot treat it as an association, so it cannot be preloaded on its own.

Ruby on Rails relationship model

In Ruby on Rails 4, how do you create a many-to-many relationship inside a relationship model for a friends list such as Facebook using the has_many :through ... syntax ?? I'm a newbie and currently learning Ruby on Rails 4. I have looked at this link.
But still have a hard time grasping it.
you will need a join table that references both sides of the relations
let us say you have an relation Post and another relation Category with a many to many relationship between them you need a join table to be able to represent the relationship.
migration for a join table would be
class CreateCategoriesPosts < ActiveRecord::Migration
def change
create_table :categories_posts do |t|
t.integer :category_id
t.integer :post_id
t.timestamps
end
add_index :categories_posts, [:category_id, :post_id]
end
end
and in the models/post.rb
Class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
and in the models/category.rb
Class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
more here:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
I think #RAF pretty much nailed it. But to use the OP's example:
class User < ActiveRecord::Base
has_and_belongs_to_many :users_list
end
class UsersList < ActiveRecord::Base
has_and_belongs_to_many :users
end
Although at first it might seem like a User should have only one list of friends (UsersList), that might not always be the case. Think of types within the UserList model, such as: 'close friends', 'work friends', 'all friends' for example.
My advice: dig into the Rails guides. This is a concept worth learning and truly understanding (which I'm still doing :).
many-to_many relationships are a simple concept, but complex when using the database because of the way databases work. A person could have 1 to N different friends, which means that a single entry for a database would need a dynamic amount of memory for each entry, which in the db world is a no-no. So instead of creating a list of friends you would have to make a table that represents the links between friends, for example:
friendship.rb
class Friendship < ActiveRecord::Base
belongs_to :friend, foreign_key: 'friend_A' # this entry has a field called 'friend_A'
belongs_to :friend, foreign_key: 'friend_B' # this entry has a field called 'friend_B'
end
These links will represent your network of friends. However, as the two previous answers have mentioned, Rails has some nifty magic, "has_and_belongs_to_many", which will do this for you.
NOTICE: The problem here is that in my StatusesController, in the index action, the #relationship object only gets the statuses of all your friends, but does not get your own statuses. Is there a better way of approaching this? I am trying to create a view to view all statuses of users that are your friends, and your own statuses too, and so far, I can't seem to figure out how to order it chronologically, even if in my status model, i included "default_scope -> { order(created_at: :desc) } ". Any advice would be deeply appreciated
class User < ActiveRecord::Base
has_many :relationships
has_many :friends, :through => :relationships
has_many :inverse_relationships, class_name: 'Relationship', foreign_key: 'friend_id'
has_many :inverse_friends, through: 'inverse_relationships', :source => :user end
#
class Relationship < ActiveRecord::Base
# before_save...
belongs_to :user
belongs_to :friend, class_name: 'User'
end
#
class RelationshipsController < ApplicationController
def friend_request
user_id = current_user.id
friend_id = params[:id]
if Relationship.where( user_id: user_id, friend_id: friend_id, accepted: false).blank?
Relationship.create(user_id: user_id, friend_id: friend_id, accepted: false)
redirect_to user_path(params[:id])
else
redirect_to user_path(params[:id])
end
end
def friend_request_accept
# accepting a friend request is done by the recipient of the friend request.
# thus the current user is identified by to_id.
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
if Relationship.exists?(relationship) and relationship.accepted == false
relationship.update_attributes(accepted: true)
end
redirect_to relationships_path
end
def friend_request_reject
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
relationship.destroy
redirect_to relationships_path
end
################################
def index
#relationships_pending = Relationship.where(friend_id: current_user.id, accepted: false)
end
end
#
class StatusesController < ApplicationController
def index
#status = Status.new
#relationship = Relationship.where('friend_id = ? OR user_id = ?', current_user.id, current_user.id).
where( accepted: true)
end
def new
#status = Status.new
end
end
#

Include array when creating a model

I have a discussion forum where users can see a list of unread posts. The way I'm doing this is to use a Look, User and Post model:
class Look < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts, through: :looks
has_many :looks
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :looks
has_many :users, through: :looks
end
So the way this works is that there is a list of all post IDs a user has viewed. It's created through the 'show' method:
def show
if current_user
viewer = current_user
view_ids = viewer.posts.pluck(:id).uniq
not_viewed = Post.where("id not in (?)", view_ids)
not_viewed_ids = not_viewed.pluck(:id)
unless Post.find(params[:id]).in?(not_viewed_ids)
Look.create(user: current_user, post: #post, viewstamp: Time.now)
end
end
end
This all works fine so far. The problem is I want to create a Look for all posts, so that I can essentially 'mark all as read'. This line works fine for creating a Look for the current post:
unless Post.find(params[:id]).in?(not_viewed_ids)
Look.create(user: current_user, post: #post, viewstamp: Time.now)
end
...but how do I make one that creates a Look for every post? Like this:
Look.create(user: current_user, post: [NEED ARRAY OF POSTS HERE], viewstamp: Time.now)
The reason I want to do this is so a user can mark all posts as read.
You can create the Look automatically just by adding the users to the posts.
Post.all.each { |p| p.users << current_user; p.save }

Active Relation: Retrieving records through an association?

I have the following models:
class User < ActiveRecord::Base
has_many :survey_takings
end
class SurveyTaking < ActiveRecord::Base
belongs_to :survey
def self.surveys_taken # must return surveys, not survey_takings
where(:state => 'completed').map(&:survey)
end
def self.last_survey_taken
surveys_taken.maximum(:position) # that's Survey#position
end
end
The goal is to be able to call #user.survey_takings.last_survey_taken from a controller. (That's contrived, but go with it; the general goal is to be able to call class methods on #user.survey_takings that can use relations on the associated surveys.)
In its current form, this code won't work; surveys_taken collapses the ActiveRelation into an array when I call .map(&:survey). Is there some way to instead return a relation for all the joined surveys? I can't just do this:
def self.surveys_taken
Survey.join(:survey_takings).where("survey_takings.state = 'completed'")
end
because #user.survey_takings.surveys_taken would join all the completed survey_takings, not just the completed survey_takings for #user.
I guess what I want is the equivalent of
class User < ActiveRecord::Base
has_many :survey_takings
has_many :surveys_taken, :through => :survey_takings, :source => :surveys
end
but I can't access that surveys_taken association from SurveyTaking.last_survey_taken.
If I'm understanding correctly you want to find completed surveys by a certain user? If so you can do:
Survey.join(:survey_takings).where("survey_takings.state = 'completed'", :user => #user)
Also it looks like instead of:
def self.surveys_taken
where(:state => 'completed').map(&:survey)
end
You may want to use scopes:
scope :surveys_taken, where(:state => 'completed')
I think what I'm looking for is this:
class SurveyTaking < ActiveRecord::Base
def self.surveys_taken
Survey.joins(:survey_takings).where("survey_takings.state = 'completed'").merge(self.scoped)
end
end
This way, SurveyTaking.surveys_taken returns surveys taken by anyone, but #user.survey_takings.surveys_taken returns surveys taken by #user. The key is merge(self.scoped).
Waiting for further comments before I accept..

Searching polymorphic associations in Rails

I have a need in my app to allow users to bookmark a post. They should only be able to create one bookmark per post. I've set up my polymorphic association like so:
class Post < ActiveRecord::Base
has_many :bookmarks, :as => :bookmarkable
end
class Bookmark < ActiveRecord::Base
belongs_to :bookmarkable, :polymorphic => true
belongs_to :user
end
class User < ActiveRecord:Base
has_many :posts
has_many :bookmarks
end
In my view, a user can create a bookmark. I would like to find some way to replace the "Create Bookmark" view code with "Delete Bookmark" code, if the user has already bookmarked a particular post.
If I try to do something like this:
#post = Post.find(params[:id, :include => [:bookmarks]])
- if #post.bookmarks.users.include?(#user)
I get a No Method error for "users"
How can I access owners of the bookmarks, to determine if the current user has already bookmarked a page?
Thank you.
I would approach this from the user's point of view:
class User < ActiveRecord::Base
has_many :posts
has_many :bookmarks
# Rails 3
def bookmarked?(post)
bookmarks.where(
{
:bookmarkable_id => post.id, :bookmarkable_type => post.class.name
}
).count > 0
end
# Rails 2
def bookmarked?(post)
bookmarks.find(:all, :conditions =>
{
:bookmarkable_id => post.id, :bookmarkable_type => post.class.name
}
).count > 0
end
end
if #user.bookmarked?(#post)
# Show delete link
else
# Show bookmark link
end
I would also advice you to add a validation to your bookmarks model that prevents a user from bookmarking the same post twice.

Resources