Show posts for:
Disclaimer: I am a complete Rails n00b.
I need to present forum users with a list of posts where some may be "unread". By unread I mean that the post has a newer: update_at timestamp than the last timestamp of the user of that post. I cannot find the best approach for this - unread messages will obviously be unique to each user.
I tried to use the "Unread" gem, but the documentation is beyond my comprehension, I cannot get it to work (and I'm not even sure if it does what I want it to do).
What's the best Rails-y way to do this?
I'm on Rails 4.1.6
I currently have a user model and a post model:
class User
has_many :posts
end
class Post
belongs_to :user
end
See below for editing.
EDIT 1:
I tried to follow the example below for adding a Model Looks and I think I am much closer to a solution (although not really). Here's what I did:
1) rails g model Look post:references user:references
+ rake db:migrate
. This obviously spawned the required model:
class Look
belongs_to :post
belongs_to :user
end
2) Edited my user and post models:
class User
has_many :posts, through: :looks
has_many :looks
end
class Post
belongs_to :user
has_many :looks
has_many :users, through: :looks
end
3) Went to rails console
:
user1 = User.first
post1 = Post.first
post2 = Post.last
look = Look.create(user: user1, post: post1)
look = Look.create(user: user1, post: post2)
4) Now I tried to spit out the results:
seen = user1.posts
seen.map(&:title)
This works great, it gives me the result user1
after seeing these two messages.
5) Then I tried to just spit out the noticed post IDs:
ids = Look.where(user: user1).pluck(:post_id)
This also works great, I get a map of visible messages ids => [2, 30]
6) Then I managed to get around the duplicate IDs by putting .uniq
at the endUser.first.posts.map(&:id).uniq
7) This is where I got stuck! :
Applying has_many :posts, through: :looks
destroys the current relationship between the user and the message (user_id is not included when the message is created). Here is my PostsController:
def create
@post = current_user.posts.build(post_params)
if @post.save
redirect_to @post
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:title, :content, :sticky, :ama_post, :post_url)
end
This is the last hurdle. I just need to make sure the user_id is included when posting.
source to share
Assuming you have a typical setup with many users and many posts, and you want to track if any user has viewed any post, then you need to link users and posts using a third model that attaches to a specific user for a specific position. ...
Start with the Rails tutorial: http://guides.rubyonrails.org/association_basics.html
Examples of models:
class User < ActiveRecord::Base
has_many :looks
has_many :posts, through: looks
end
class Post < ActiveRecord::Base
has_many :looks
has_many :users, through: looks
end
class Look < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
Let's set up some sample data using the rails console:
alice = User.create(name: "Alice")
post1 = Post.create(name: "Post 1")
post2 = Post.create(name: "Post 2")
post3 = Post.create(name: "Post 3")
post4 = Post.create(name: "Post 4")
look = Look.create(user: alice, post: post1)
look = Look.create(user: alice, post: post2)
To find posts the user has seen:
seen = alice.posts
seen.map(&:name)
=> ["Post 1", "Post 2"]
To find messages that the user has not seen:
ids = seen.map(&:id)
unseen = Post.where("id not in (?)", ids)
unseen.map(&:name)
=> ["Post 3", "Post 4"]
This is an easy way to get the records you want.
Once you get this working, there are better ways to get records through query optimization.
For example, you can get much less data by getting only the ID numbers. Here are some ways.
# Basic: traverses two joins, then loads all columns
ids = Post.joins(:looks => :user).where("users.id = ?", alice.id).map(&:id)
# Faster: traverses two joins, then loads one column
ids = Post.joins(:looks => :user).where("users.id = ?", alice.id).pluck(:id)
# Fastest: no joins, and only loads one column
ids = Look.where(user: alice).pluck(:post_id)
Take a look at the unread pearl for tutorial and example code.
source to share
You will probably need some kind of history journal. He can either register individual copies, or simply register the last visit to the forum.
Here's a hypothetical scenario:
- There is only one forum group in your forum.
- You have a user model for each user.
- You have a Topic / Topic containing a separate topic, with a has_many relationship to:
- Post model that contains a specific post created by a specific user (not relevant to the answer, but part of the script)
- Your forum has a theme controller with standard RESTful resource routes.
What you want to do is split / highlight / mark unread topics from read topics, right? Breaking this down, you need:
- Find out when the user last visited the forum index page.
- Get a list of topics.
- When rendering each theme in the list, determine if the theme is more "recent" than the user's last visit. Whether it was created after the last visit or after the post was posted.
- Give it away in a different way (or whatever) as per your requirement.
Turning this into a very simple implementation, you should:
- Add the attribute "last hit index" to the user.
- Load this value when visiting the index.
- Provide relevant topics.
- Update the last visit attribute of the index for the current user (best implemented as after_action)
This implementation now assumes that you only want to track it in one place and in order with the constraints associated with this selection (reading a single topic will not mark it as read).
If you so desire, you can overcome this limitation by creating a table that belongs to both the user and the topic, and then update that table whenever the user "reads" the topic (depending on whether you want them to view topic or just see it in the index).
EDIT
To ultimately answer this question, you need to break it down into more details. "Identify unread topics" means that you need to answer:
- When do I believe the topic is being read? This is when I look at individual posts, so how is it "new"? Is it when I see a topic on the list after it's "new"?
- What do I consider to be a new / updated theme? This is when it was sent first? what happens if someone posts a response?
EDIT 2
Then I would do the following:
- Add a statement
touch: true
to the Post model association to a topic so that the updated_at attribute on that topic will be changed whenever another post is made / edited. - Create a helper view method to determine if the last current read (if any) is the last one than the Topic updated_at timestamp.
- Create
after_action
only onPosts#show
that creates / updates the Read for this user and topic.
source to share