Select multiple values from associated model rails - ruby-on-rails

Assuming I have this association
User have_many posts
Post belongs_to user
User Post
----------------
id id
name title
user_id
How to list only post title and username with includes/joins ?
(list of posts [title - username])
#posts = Post.includes(:user).select('........')
don't offer this
#posts = Post.all.each {|p| p.user.username}
__________________UP_____________________
It worked for joining 2 tables.
What if I want to use it for more complex example?
check out my prev question optimize sql query rails
#Humza's answer partly worked.
it might be something like this
#posts = Post.joins(:user, :category).paginate(:page => params[:page]).order("created_at DESC")
but It doesn't display posts that don't have category
I also need to display gravatar but I think I can just use user.email as usr_email and use gravatar_for (post.usr_email) but I'll have to customize gravatar helper for this.
posts_controller.rb
def index
#posts = Post.includes(:user).includes(:comments).paginate(:page => params[:page]).order("created_at DESC")
end
index.html.erb
<%= render #posts %>
_post.html.erb
<%= gravatar_for post.user, size:20 %>
<%= link_to "#{post.title}", post_path(post) %>
<%= time_ago_in_words(post.created_at) %>
<%= post.comments.count %>
<%= post.category.name if post.category %>

Take a look at pluck.
Post.joins(:user).pluck(:title, :name)
Note that it works in this case because there's no ambiguity regarding the name column, you might want to specify the table explicitly (pluck(:title, "users.name")).

includes is used in case of eager-loading. You need joins in this case.
posts = Post.joins(:user).select("posts.title AS title, users.name AS username")
You can access the values then in the following way:
post = posts.first
post.title # will give the title of the post
post.username # will give the name of the user that this post belongs to
If you can pluck multiple columns, then the following might be more useful to you:
posts = Post.joins(:user).pluck("posts.title", "users.name")
The result will be a 2D array, with each element being an array of the form [post_title, post_username]

Post.joins(:user, :category)
but It doesn't display posts that don't have category
That's because joins uses INNER JOIN to join the tables together. If you want to everything from Post even though the particular record doesn't have its counterpart in the other table, you need to use LEFT JOIN. Unfortunately ActiveRecord doesn't have a nice way of generating it and you will need to do that manually:
Post.joins("LEFT OUTER JOIN categories ON categories.post_id = posts.id")...
See A Visual Explanation of SQL Joins for more information.

You can call array methods on a scope so:
Post.includes(:user).map { |p| [p.title, p.user.name] }
will get the posts with included user and map each post to a tuple of the post title and the user name.
That may not entirely answer your question as I think you might want to restrict the results of the query to just the required fields in which case, I think you can add a .select('title', 'users.name') to the query. (Not in a position to test at the moment)

Related

Doing group_by on class with belongs_to in Rails

I have a model course which has_many subcategories. I want to build a page that shows courses grouped by their subcategory. So far, I have
#courses = Course.personal_enrichment.order('subcategory_id').page params[:page]
#courses_facet = #courses.group_by(&:subcategory_id)
which works fine, but I need to show the actual subcategory name in the view, not the number. I've seen some other answers about this type of thing, but most of them assume the attribute you're grouping by is already human readable. Maybe I'm missing something?
When rendering the view you can just access the referenced models' attributes. Since group_by returns a hash, you could do something like this:
<% #courses_facet.each do |subcategory_id, courses| %>
<% subcategory_name = courses.first.subcategory.name rescue nil %>
<label><%= subcategory_name %></label>
<% end%>
Unless relevant subcategory models are cached this will generate N+1 queries to fetch the subcategory names. One way to avoid that is to include subcategory records to the initial resultset.
#courses.includes(:subcategories).group_by(&:subcategory_id)

How do I use a button to update the order of a filtered category?

I am new to Rails, but slowly making progress. I can't quite wrap my head around how to achieve my next task.
I have a controller (IdeasController) with an index that looks like this:
def index
if params[:round].blank? && params[:challenge].blank?
#ideas = Idea.all.order(params[:sort])
# #ideas = Idea.all.order(created_at: :desc, cached_votes_up: :desc)
end
if params[:round].present?
#round_id = Round.find_by(name: params[:round]).id
#ideas = Idea.where(round_id: #round_id).order("created_at DESC")
end
if params[:challenge].present?
#challenge_id = Challenge.find_by(name: params[:challenge]).id
#ideas = Idea.where(challenge_id: #challenge_id).order("created_at DESC")
end
end
I am updating the view and filtering by category with the above :round and :challenge with the code below in my index.html.erb:
<%= link_to "All", ideas_path %>
<% Round.all.each do |round| %>
<%= link_to round.name, ideas_path(round: round.name) %>
<% end %>
<% Challenge.all.each do |challenge| %>
<%= link_to challenge.name, ideas_path(challenge: challenge.name) %>
<% end %>
Now, my problem is that I want to create a button that orders by created_at DESC or ASC. I want the button to essentially be a toggle. I also want another button to order by cached_weighted_average DESC or ASC. This is from acts_as_votable so I can sort by vote counts.
The problem I am running into is that I can create a link or button that orders by created_at or cached_weighted_average, but it replaces all of the URL that was previously filtered by :round or :challenge. For example, if a user clicks "Round 1" and sees all ideas marked for "Round 1" and then they click the link to order by cached_weighted_average, the URL replaces:
/ideas?round=Round+1
With this:
/ideas?sort=cached_weighted_average+ASC
What I want is:
/ideas?round=Round+1&?sort=cached_weighted_average+ASC
I know this is a very new question, but everything I have tried has failed so far. It feels like I am missing something very easy. What I noticed I can do easily is inside the controller I can do something like:
if params[:round].present?
#round_id = Round.find_by(name: params[:round]).id
#ideas = Idea.where(round_id: #round_id).order("cached_weighted_average DESC")
end
Which is perfect. This button just needs to switch between cached_weighted_average DESC and created_at DESC.
Any help is appreciated, thanks.
passing multiple parameters is one way to handle:
<%= link_to object.name, object_path(first: something, second: something_else) %>
then alter your conditionals to contemplate presence of multiple params.
to differentiate between round and challenge when attempting to allow the user to choose how they'd like to sort you could use the same name and then pass it different values.
something like:
params["round_or_challenge"]
this would change your conditional to something like:
if params["round_or_challenge"] == "round" && params["asc_or_desc"] == "asc"
# query
elsif params["round_or_challenge"] == "challenge"
# query
end
or whatever. it's basically the same...just pass the values you need. you can also pass the existing parameters from the view the same way you access them in the controller.
Thanks for the response, #toddmetheny. I didn't implement your solution, but your solution helped me understand passing multiple parameters a bit more.
I ended up creating a helper, sortable. I also used the url_for to append at the end of whatever the current URL might be. I liked this approach because it meant I could sort on any parameter. I'm not sure that it's the best solution, but it works.
def sortable (name, sort)
link_to name, url_for(params.merge(sort: sort))
end

populate object_id fields in rails query

I have two models. One named user, and one named orders. Users have an order_id field with the order's id. I have set up orders to belong to users and users to have many orders.
Now I would like to have a view where I show some users, and each order for that user. Obviously it would be nice if I could just get one bit object that has the users and instead of the order_id, have the actual order. I know there are some languages where you can something like this:
found_user.populate(order_id)
Is there an option to do this in rails? I have seen the select function, but it doesn't seem to work, not with User.find() anyhow, which is what I am using.
Any ideas?
When you say order belongs to user, you should have user_id in orders table but not order_id in users table.
Now I would like to have a view where I show some users, and each
order for that user.
And now in the view where you want to display the users and their orders, try the below code
<% #users.each do |user| %>
<%= user.name %> #assuming you have name field in users table.
<% user.orders.each do |order| %>
<%= order.name %> #assuming you have name field in orders table.
<% end %>
<% end %>
Where #users = User.all which is defined in the respected controller action.

Creating a feed with acts_as_taggable_on in Rails

I'm trying to create a feed in Rails utilizing acts_as_taggable_on and I'm having a bit of trouble.
In my app there are Users who belong to Groups and every User selects several Tags for himself/herself using the acts_as_taggable_on context :user_tags. The Users create Posts which also have Tags, but of the context :post_tags.
What I'm trying to do is create a feed for the current User comprised of Posts from within his/her Group that have at least one :post_tag in common with the current User's :user_tags. So for example, if I have a User that chooses "Developer, Designer" as two :user_tags, I'd want to return all Posts from within his/her Group that have "Developer" and/or "Designer" as :post_tags.
After hours of fiddling around, here's what I tried in post.rb, but it gave me an error ("ERROR: column 'tag_id' does not exist LINE 1"), which seems odd given that tag_ids do exist for taggings.
# Returns posts with tags also attributed to the given user.
def self.from_posts_matching_tags_of(user)
matching_tag_ids = "SELECT tag_id FROM taggings
WHERE taggable_id = :user_id"
where("tag_id IN (#{matching_tag_ids}) OR user_id = :user_id", user_id: user)
end
Any suggestions for how to properly create such a feed method would be much appreciated. And if there's any other relevant code you want me to share, just let me know.
So, I figured out a solution that has worked thus far in my (admittedly limited) testing. Hope this is helpful to anyone else seeking to do something similar. Of course, if anybody knows a more efficient way to tackle this, I'm always open to improvements.
I added this to pages_controller.rb and it seems to do the trick:
def index
if logged_in?
#feed_items = Post.tagged_with([#current_user.user_tag_list], :on => :post_tags, :any => true)
end
end
And this on index.html.erb:
<% if #feed_items.any? %>
<% #feed_items.each do |post| %>
<%= link_to post.content, post %><br/>
<% end %>
<% end %>

how to separate numerous hashtags onto a separate page of a user post?

On my website, I allow users to make a post
This is what the current Post create controller looks like
def create
#post = current_user.posts.build(params[:post])
if #post.save
flash[:success] = "Posted!"
redirect_to home_path
else
#feed_items = []
render 'static_pages/home'
end
end
I would like to create a new page called "browse" and I want to list all of the hashtags in descending order. These hashtags will be pulled from the above posts. What is the best way to pull the hashtags (could be 1 or 2+ hashtags used in a single post) and display them in this new browse page?
You can use a regular expression to pull hashtags out of text. There's a thread here: Best HashTag Regex That post is about .NET, but regular expressions are pretty universal, so you should be able to use something there. (I recommend Rubular for testing regular expressions). This one seems popular:
hashtag_regex = /\b#\w\w+/
Now, you have a choice about when you pull the hashtags out in this way:
Option One
You can pull them out 'in real time', when the user loads the Browse page. This will probably be simpler to code, but is less flexible - you probably wouldn't be able to do things like search for posts by hashtag, or count the number of appearances of a particular hashtag. In this case, you can extract the hashtags in the view rendered by the Browse page, for instance, something like this:
<% #posts.each do |post| %>
Text: <%= post.text %>. Hashtags: <%= post.text.scan(hashtag_regex).join(',') %>
<% end %>
Option Two
You can extract hashtags when the Post is created/edited, and store them, either in a new field on the Post model, or in an associated model (Post has_many :hashtags), something like this:
def create
#post = current_user.posts.build(params[:post])
#post.hashtags = #post.text.scan(hashtag_regex)
Or as a callback on the post model:
class Post < AR:B
before_save :extract_hashtags
def extract_hashtags
hashtags = text.scan(hashtag_regex)
end
end
Then simply display them:
<% #posts.each do |post| %>
Text: <%= post.text %>. Hashtags: <%= post.hashtags %>
<% end %>
This approach is probably slightly more efficient, and by placing the hashtags in your database in their own field or table, you'll have the ability to sort by them, search for them, or count them.
I hope this is helpful.

Resources