Ruby find User with specific Post contents - ruby-on-rails

Class User
has_many :posts
end
Class Post
belongs_to :users
end
I have a post's content and would like to find out if a user ever sent a post with the same content. I could find it out easily if I was only searching against a single user.
user.posts.exists?(content: params[:content])
I would like to look through all the users in the DB.
users = Users.all
users.posts # will not work NoMethodError: undefined method `posts' for #<Array:0x007fbe818fbd98>
I need either a true as soon as there is a single match or a false if there is no user with matching content, for all the posts posted from all the users.
I could always loop this in a block and get a result this way, is there a way of doing this in a single line of code?

Try something like this:
posts = Post.where(content: params[:content])
users = User.find posts.map(&:user_id)
Or a shorter one:
users = User.find Post.where(content: params[:content]).pluck(:id)
Or if you just need to check whether a post (created by any user) with a given content exist:
Post.exists?(content: params[:content])

Related

Specify table for rails .where

Can't find same question. How can I specify select.where for model?
I need to select from different tables by one model and want to get something like this in controller:
params[:id] = 1248 // here is example of request params
id=params[:id] // this id goes to message SQL like table name with prefix:
Message(id).all => select * from messages_1248
How can I get something like this?
Thanks for answers!
UPD:
I have one table with users and many tables with messages (each table is for one pair of users). In users table there is 'messages' column with messages tables id's. And inside user_controller I need to run a query like in my question. Maybe anybody can share an example?
how about little bit change the design, with just 2 tables (user and message) just idea with details below
user table (id,name)
messages table(user_id,message_text)
you setup the relation user has_many messages (please see this link for more guide http://guides.rubyonrails.org/association_basics.html#the-has-many-association
user.rb
has_many :memberships
message.rb
belongs_to :user
for example you need to access user with specific id and the messages for this user
inside users_controller.rb
def show
#user = User.find(params[:id])
# this find user
#messages = #user.messages
# get all the messages for specific users
end

How to access variables in model

I am trying to get access to a property contained inside my user object.
My user model has_many: posts. In the controller how would i gain access to these posts? Would i create a method in the model?
def posts
#posts = Post.find(User_id: params[:id])
end
or can i directly access the posts for the user. User.posts Since i am currently residing in the controller, is the controller aware of the currently selected model? Or do i have to pull the information again?
You can query the database for all the posts with a specific user_id, like this:
#posts = Post.where(user_id: params[:id])
Alternatively, you can find the user first and then fetch all posts associated with that user, like this:
user = User.find(params[:id])
#posts = user.posts
Assuming your id in params is the id of your user, you can use user = User.find(params[:id]) to get the user and #posts = user.posts to get all the posts of this user.
So, it is not about where you are, It is about what you are calling.
I'm sure you are familiar with relationships...
When you have relationships, it means that you can get to one relation from the other through whatever association exists between them.
If I am my father's son, then you can get me directly by checking my father's children. ( you don't necessarily have to get all children in the village first )
So, bringing all my story above together, with the association between your Post and User, you can always call user.posts (user being an instance of User) and post.user ( with post being an instance of Post)
The Ruby on Rails guides have a section on associations, which is what you want. It's here: http://guides.rubyonrails.org/association_basics.html
In a nutshell, because you have added an association in your user model to a number of post records, Rails will build a helper method in your user model called posts. You can use that to access all the posts associated with that user.
When you create a post, the post record needs to have a column called user_id. This will provide the 'physical' link between the user and post models. You can access the posts from a user like so:
user.posts each do |post|
# do something with post.content
end
To get posts that match some criteria in the posts collection you can query like this:
posts = user.posts.where(:something => 'matches criteria')
If you know there's only one post that matches the criteria, you can do this:
post = user.posts.where(:something => 'matches criteria').first
The post model also needs a belongs_to :user association. (The belongs_to will generate a helper method called user in the post model which you can then use to access the user record from the post.) For example:
user_email = post.user.email
The user record does not require a post_id column since Rails knows that user.post refers to the post table and automagically generates a query using user_id.
Anyway, the guide I linked to above will give you all the information you need and more too.

Show unread posts in Rails

Disclaimer: I'm a complete Rails n00b.
I need to present my forum users with a list of posts, where some might be 'unread'. By unread I mean that the post has a newer :updated_at timestamp than the user's last view timestamp of that post. I can't figure out the best approach for this - unread posts would obviously be unique to every user.
I tried using the 'Unread' gem but the documentation is above my comprehension, I can't get it to work (and I'm not even sure it does what I want it to do).
What's the leanest most 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 edits.
EDIT 1:
I tried following the example below of adding a Looks model, and I think I'm much closer to a solution (although not all the way there). Here's what I did:
1) rails g model Look post:references user:references + rake db:migrate. This obviously generated the model needed:
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 into 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) I now tried to spit out the results:
seen = user1.posts
seen.map(&:title)
This works fine, it gives me the result of user1 having seen those two posts.
5) Then I tried just spitting out the IDs of the seen posts:
ids = Look.where(user: user1).pluck(:post_id)
This also works fine, I get a map of seen post ids => [2, 30]
6) I then managed to get around duplicate IDs by putting a .uniqat the end of User.first.posts.map(&:id).uniq
7) This is where I get stuck!:
Applying has_many :posts, through: :looks ruins the current relationship between a user and a post (the user_id is not included when creating a post). 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 creating a post.
Presuming you have a typical setup with many users and many posts, and you want to keep track whether any user has looked at any post, then you need to associate the users and posts by using a third model that joins a specific user to a specific post.
Start with the Rails guide: http://guides.rubyonrails.org/association_basics.html
Example 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 the posts that a user has seen:
seen = alice.posts
seen.map(&:name)
=> ["Post 1", "Post 2"]
To find the posts that a user has not seen:
ids = seen.map(&:id)
unseen = Post.where("id not in (?)", ids)
unseen.map(&:name)
=> ["Post 3", "Post 4"]
This is a simple way to get the records you want.
After you get this working, there are better ways to get the records by using query optimizations.
For example, you can retrieve much less data by getting just the id numbers. Here are a few 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 gem for guidance and example code.
You'll probably need some sort of historical log. It can either log individual instances, or merely log the latest visit to the forum.
Here's a hypothetical scenario:
Your forum has only one forum group.
You have a User model for each user.
You have a Topic/Thread model that contains the individual topic, with a has_many relationship to:
The Post model which contains a specific post authored by a specific user (not relevant to answer but part of scenario)
Your forum has a Topic controller with the standard RESTful resource routes assigned.
What you want to do is separate/highlight/designate the unread topics from the read topics, right? Breaking that down, you need to:
Figure out when the user last visited the forum's index page.
Get a list of topics.
When rendering each topic in the list, determine whether the topic is more "recent" than the user's last visit. Regardless of whether it was created after the last visit, or had a post after the last visit.
Render it differently (or whatever) as per your requirements.
Turning that into a very simple implementation, you would:
Add a "last index visit" attribute to the User.
Load that value when visiting the index.
Render the topics accordingly.
Update the last index visit attribute on the current user (best implemented as an after_action)
Now this implementation assumes that you only want to keep track of it in one place and are OK with the limitations associated with that choice (reading an individual topic won't mark it as read).
If you wanted to, you could eliminate that limitation by creating a table that belongs to both the user and the topic, and then updating that table whenever the user 'reads' the topic (depending on whether you want them to view the 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 you need to answer:
When do I consider a topic read? Is it when I view the individual posts since it is 'new'? Is it when I see the topic in a list after it is 'new'?
What do I consider a new/updated topic? Is it when it's first posted? what happens if someone posts a reply?
EDIT 2
Then I'd do the following:
Add a touch: true statement to the Post model's association to the Topic to ensure the updated_at attribute on the topic is modified whenever another post is made/edited.
Create a view helper method to determine whether the current user's last reading (if any) is later than the Topic's updated_at timestamp.
Create an after_action only on Posts#show that creates/updates the Reading for that user & topic.

How to obtain BEFORE and AFTER state of my object's related attributes?

In my application I have the following relationship:
Document has_and_belongs_to_many Users
User has_and_belongs_to_many Documents
What I am trying to figure out is how to perform the following:
Let's say a document has 3 users which belong to it. If after an update they become for ex. 4, I would like to send an email
message (document_updated) to the first 3 and a different email message (document_assigned) to the 4th.
So I have to know the users belonging to my Document BEFORE and AFTER the Document update occurs.
My approach so far has been to create an Observer like this:
class DocumentObserver < ActiveRecord::Observer
def after_update(document)
# this works because of ActiveModel::Dirty
# #old_subject=document.subject_was #subject is a Document attribute (string)
# this is not working - I get an 'undefined method' error
#old_users=document.users_was
#new_users=document.users.all.dup
# perform calculations to find out who the new users are and send emails....
end
end
I knew that my chances of #old_users taking a valid value were slim because I guess it is populated dynamically by rails via the has_and_belongs_to_many relation.
So my question is:
How do I get all my related users before an update occurs?
(some other things I've tried so far:)
A. Obtaining document.users.all inside DocumentController::edit. This returns a valid array, however I do not know how to pass this array to
DocumentObserver.after_update in order to perform the calculations (just setting an instance variable inside DocumentController is of course not working)
B. Trying to save document.users inside DocumentObserver::before_update. This is not working either. I still get the new user values
Thanks in advance
George
Ruby 1.9.2p320
Rails 3.1.0
You could use a before_add callback
class Document
has_and_belongs_to_many :users, :before_add => :do_stuff
def do_stuff(user)
end
end
When you add a user to a document that callback will be called and at that point self.users will still it yet contain the user you are adding.
If you need something more complicated it might be simpler to have a set_users method on document
def set_users(new_user_set)
existing = users
new_users = users - new_user_set
# send your emails
self.users = new_user_set
end

Adding a "Like/Unlike" button to a post in Rails

The site is a simple community where each user creates posts and users may "like" them or "unlike" them.
I have a Post and a Like model. Currently, I'm listing all posts and also the likes size for each post through post.likes.size . The button to like a post is also working.
What i don't know how to do is how to depending on the case, if a post should show the unlike button or the like (depending if the current_user already liked that post).
The like model is very simple:
User_id // current user
Post_id // post to associate
Thanks in advance!
You should define association in user model
if it's ror 2.* add method in User model. it should look like this:
has_many :likes
def already_likes?(post)
self.likes.find(:all, :conditions => ['post_id = ?', post.id]).size > 0
end
Assuming Like has fields user_id and post_id
and of course in view
if current_user.already_likes?(#post)
#add unlike button
end
You want to search for a record that matches the user_id and post_id. If you find one, you want to show the 'unlike' button, b/c that means the user has 'liked' the post already. If you don't (it returns nil), you want to show the 'like' button.
The following method returns nil if the user hasn't 'liked' the post, and not nil if the user has 'liked' the post.
def user_likes(current_user, post_id)
likes.find(:first, :conditions => ['user_id = ? AND post_id = ?', current_user, post_id] ).nil?
end
So you can say:
if user_likes(1, 12).nil?
# show like button
else
#show unlike button
end
Also you could add validation to your Like model like so:
validate :user_does_not_already_like_post
def user_does_not_already_like_post
errors.add(:user, "You can only like a post once.") if user.already_likes?(post)
end
create an action like this in your posts controller.
def unlike
# get the post
#code to decrement the like counter of a specific post
end
then from your view, create a button or a link that points to this action.

Resources