I have two models blog and comment. Blog contains multiple comments, comment can only belong to 1 blog.
For showing the API like
blog/2/comment/1
I have to use the following code inside the comment controller
def show
#blog = Blog.find(params[:blog_id])
#comments = #blog.comments
#comment = #comments[params[:id].to_i - 1]
end
I feel this code is quite akward, especially the last one #comments[params[:id].to_i - 1]. I have to convert parameter id to integer, then change to 0 based array index. Any refactoring can happen here
Do you mean make it look more "Rails"? Like this?
def show
#blog = Blog.find(params[:blog_id])
#comment = #blog.comments.find params[:id]
end
As per OP's comment. Your transformation is necessary. However, you can avoid loading the entire set of comments and only take the one you need.
#comment = #blog.comments.offset(params[:id].to_i - 1).limit(1)
Related
I have 3 scaffolds that resources are nested as following: mangas > chapters > scans. I'm trying to create a website that allows visitors to read mangas chapters, but I'm getting some difficulties to set up the scan_controller's setter.
I have some constraints to follow: the manga scaffold uses FriendlyId, and both chapters and scans are shown to the URL via their chapter/scan number (an integer)
So, for the manga controller, i did the following:
private
def set_manga
#manga = Manga.friendly.find(params[:id])
end
I simply followed the manuel, no problem whatsoever.
For the chapter_controller, I did this
private
def set_chapter
#manga = Manga.find_by(slug: params[:manga_id])
#chapter = #manga.chapters.find_by(chapter_number: params[:id])
end
This allows me to get all chapters that are linked to the manga I want, and only them. Plus, I get to pass the chapter_number as an id into the link.
And, lastly, I tried this for the scan_controller:
private
def set_scan
#manga = Manga.find_by(slug: params[:manga_id])
#chapter = #manga.chapters.find_by(chapter_number: params[:id])
#scan = #manga.chapters.scans.find(params[:id])
end
However, at this point, I can't get a satisfying result. With this configuration, I get undefined method 'pejis' for #<Chapter::ActiveRecord_Associations_CollectionProxy:0x0000000713d1a0>. I tried to also set as follow:
private
def set_scan
#chapter = Chapter.find_by(chapter_number: params[:id])
#scan = #chapter.scans.find_by(scan_number: params[:id])
end
But i get undefined method 'pejis' for nil:NilClass, which is weird because it doesn't return any error with predefined ids in the rails console.
As anyone an idea ?
Thank you in advance
So, after re-reading again and again my code, I simply needed to do the following:
def set_peji
#chapter = Chapter.find_by(chapter_number: params[:chapter_id])
#peji = #chapter.pejis.find_by(scan_number: params[:id])
end
So I had to remove #manga since it's not concerned anymore, then to set the scan's chapter_id as being the chapter's number.
I need to display post with the latest made comment in it, on top of page. And every Post has many comments and many Comments belong to one post.
Here is my index method from Posts controller
def index
#posts = Post.all.order("posts.created_at desc")
def new
#post = Post.new
end
end
I looked through Rails docs and found .order and .where methods, and I think those two methods are the solution to my problem but I am not sure how to use it
try this:
#posts = Post.joins(:comments).order("comments.created_at DESC")
def index
#posts = Post.all.order("created_at DESC")
#latest_post = #posts.first
end
update
I interpreted word comments as posts.
update 2
First you need to find the latest comment:
#latest_comment = Comment.all.order('created_at DESC').first
Having that, you can extract the ID of the Post to which this comment belong:
post_id = latest_comment.post_id
Now you have the latest comment and the respective post's id. I would modify index page like this:
def index
#posts = Post.all.order("created_at DESC")
#latest_comment = Comment.all.order('created_at DESC').first
#post_of_latest_comment = Post.find(#latest_comment.post_id)
end
I am not sure what do you mean by displaying it on top, but I am pretty sure with this code you can do it in your view.
update 3
In your view, you hsould have something like this:
<h1>Top comment</h1>
<%= #latest_comment.text %>
By .textI mean some attribute of model Comment wich contains the content of the comment, plain text. If you need more help with this, show what atributes your Post and Coment model have.
I tried a modified line from Pitabas Prathal:
Post.joins(:comments).order("comments.created_at DESC").group('post_id')
where post_id is the foreign key in the Comment model from the Post model. It worked fine for me :)
Ok, relying on guide's blog tutorial:http://edgeguides.rubyonrails.org/getting_started.html
Trying to learn how to write a method myself.
The guide did both article and comments ie, belongs_to & has_many relationship.
So, thought why not try to find out the totality of comments.
This is the method I wrote for Comments controller:
def total_number_of_comments
#article = Article.all
#comments_total = #article.comments.count
end
Then I put this in article's view index.html.erb
<p>Total number of comments:</p>
<%= #comments_total %>
On the index page, it doesn't show anything.
So, what am I doing wrong?
And, I don't want just a "correct" answer. I'd like to understand what I'm missing here.
But what I'm befuddled here is how to think this out.
I hesitate to do this because it would prolong the post, but I thought why not try to do count of articles too.
So, here's what I did:
In Article model
def self.total_number_of_articles
Article.all.count
end
In Article controller
def total_number_of_articles
#articles_total = Article.total_number_of_articles
end
Then in index.html.erb of Article View again, I put this:
<p>Total number of articles:</p>
<%= #total_number_of_articles %>
Again, nothing shows up in terms of count in either comment or article.
So .... clearly I'm missing something here.
EDIT
The comment (total_number_of_comments) method was sorta based on this: (from railsguide)
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.create(comment_params)
redirect_to article_path(#article)
end
There are many things you missed, I would happy to explain you.
Here
def total_number_of_comments
#article = Article.all
#comments_total = #article.comments.count
end
You have to do this
def total_number_of_comments
#comments_total = Comment.all.count // simple solution
end
Next is , you didn't used proper instance variable.
def total_number_of_articles
#articles_total = Article.total_number_of_articles
end
See yourself
Total number of articles:
<%= #total_number_of_articles %> // this is wrong
You assigned #articles_total but used #total_number_of_articles. if you use #articles_total it will work fine.
You should define a function index in your controller.
calling a GET on /articles/index calls the controller function index, you should set #articles_total = Article.total_number_of_articles in your index function in controller. You have it in a function in your controller that is not getting called.
I'm looking for best practices. Here's the scenario:
Customers can pay for one or more Widgets from a form. So I have a Payments model and a Widgets model. There is no association between them (Payments is associated with Customer). What's the best way to handle this?
In the Payments controller I could do:
def create
#customer = Customer.find(params[:customer_id])
if #customer.payments.create!(params[:payment])
how-many-widgets = params[:payment][:number].to_i
while how-many-widgets > 0
widget = Widgets.new
... update widget ...
widget.save!
how-many-widgets = how-many-widgets - 1
end
end
redirect_to #customer
end
Is this the best way to do this? Or is there some more elegant solution?
If you're saving and changing things, it's a good bet you should be doing this code in a model, rather than a controller. If I were to refactor your code, it would look a little like this:
def create
#customer = Customer.find(params[:customer_id])
if #customer.payments.create!(params[:payment])
params[:payment][:number].times do
Widget.create(params[:widget])
end
end
redirect_to #customer
end
If Widget.create isn't what you're looking for, come up with a custom method that takes the params in, transforms them, and then spits out the correct object. Also if the widgets should be related to either the customer or payments, don't hesitate to relate them -- like, if you look at that code and say, "I also need to pass the current user/customer/payment to the widget," that would be a good hint that the widget should be associated to that model somehow.
What's the best way to construct a where clause using Rails ActiveRecord? For instance, let's say I have a controller action that returns a list of blog posts:
def index
#posts = Post.all
end
Now, let's say I want to be able to pass in a url parameter so that this controller action only returns posts by a specific author:
def index
author_id = params[:author_id]
if author_id.nil?
#posts = Post.all
else
#posts = Post.where("author = ?", author_id)
end
end
This doesn't feel very DRY to me. If I were to add ordering or pagination or worse yet, more optional URL query string params to filter by, this controller action would get very complicated.
How about:
def index
author_id = params[:author_id]
#posts = Post.scoped
#post = #post.where(:author_id => author_id) if author_id.present?
#post = #post.where(:some_other_condition => some_other_value) if some_other_value.present?
end
Post.scoped is essentially a lazy loaded equivalent to Post.all (since Post.all returns an array
immediately, while Post.scoped just returns a relation object). This query won't be executed until
you actually try to iterate over it in the view (by calling .each).
Mmmh, the best approach you want to use can be to spread this in 2 actions
def index
#post = Post.all
end
def get
#post = Post.where("author=?", params[:author_id])
end
IMHO it has more sense if you think about a RESTful API, index means to list all and get (or show) to fetch the requested one and show it!
This question is pretty old but it still comes up high in google in 2019, and also some earlier answers have been deprecated, so I thought I would share a possible solution.
In the model introduce some scopes with a test for the existence of the parameter passed:
class Post
scope :where_author_ids, ->(ids){ where(author_id: ids.split(‘,’)) if ids }
scope :where_topic_ids, ->(ids){ where(topic_id: ids.split(‘,’)) if ids }
Then in the controller you can just put as many filters in as you wish e.g:
def list
#posts = Post.where_author_ids(params[:author_ids])
.where_topic_ids(params[:topic_ids])
.where_other_condition_ids(params[:other_condition_ids])
.order(:created_at)
The parameter can then be a single value or a comma separated list of values, both work fine.
If a param doesn’t exist it simply skips that where clause and doesn’t filter for that particular criteria. If the param exists but its value is an empty string then it will ‘filter out’ everything.
This solution won’t suit every circumstance of course. If you have a view page with several filters on, but upon first opening you want to show all your data instead of no data until you press a ‘submit’ button or similar (as this controller would) then you will have to tweak it slightly.
I’ve had a go at SQL injecting this and rails seems to do a good job of keeping everything secure as far as I can see.
You should model url using nested resources. The expected url would be /authors/1/posts. Think of authors as resources. Read about nested resources in this guide: http://guides.rubyonrails.org/routing.html (scroll to 2.7 - Nested Resources).
Would something like this work?
def get
raise "Bad parameters...why are you doing this?" unless params[:filter].is_a?(Hash)
#post = Post.where(params[:filter])
end
Then you can do something like:
?filter[author_id]=1&filter[post_date]=... etc.