I have a model Post that has_many of model Comment.
On a Posts show action, there is a list of Comments found with #comments = #post.comments.
I also have a form for creating new Comments. The form has its object created with #comment = #post.comments.build.
This all works for listing and successfully creating comments.
The problem occurs when there is an error when the comment is submitted. The errors are shown in the same form (so, on Post#show) via render "posts/show".
In this case, I have to set #comments = #post.comments again, but this time the list of comments includes the not-yet-saved comment that the user is trying to create.
I solved it by using #post.comments.all, which only gives me the saved models, but Rails complains that this is deprecated in Rails 4.
How do I remove the unsaved comment from the list of comments I get from #post.comments?
I ran into a similar issue with a Rails 5 application. This seemed like the most straightforward approach:
#comment = #post.comments.build
#comments = #post.comments.where.not(id: nil)
You could add a scope to the comment model to find only database rows instead of in memory data, for example:
class Comment < ActiveRecord::Base
scope :by_post, ->(post) { where(post: post) }
end
called by: #comments = Comment.by_post(#post)
The most efficient way imho is just to ignore the new record in the view, instead of retrieving all comments again.
So in your view you will do something like:
= f.simple_fields_for :comments do |c|
- unless c.object.new_record?
= render 'comment_fields', :f => c
You can force load the comments. You have to do it like this:-
#comments = #post.comments(true)
Related
I have a Favorite model and an Article model. The Favorite has an attribute fav_id that is equal to the the id of Article.
I want to create a link_to #article.title and pass a param of :id=>#favorite.fav_id.
favorites controller code:
def show
#favorite = Favorite.find(params[:id])
#article = Article.find(params[:id])
end
view/favorite/show code:
<%= link_to #article.title, article_path(:controller=>:article, :id=>#favorite.fav_id, :action=>'view')
When I load the page, the favorite id is used, which is fine. I just want to pass favorite.fav_id to the #article = Article.find(params[:id]). That way, the link will show the article title instead of a number (fav_id). In the future id like to be able to show #article.description and other attributes too.
I have also considered passing the article attributes to favorite, but this seems like itd be a heavier load on the database so I've so far avoided that
I've also tried a :through => association. Maybe I did it wrong, but :through didn't work when I tried it. Any suggestions?
Rails 4.2.0
Ruby 2.1.5
If your Favorite model has an attribute, pointing to another model (Article), that means, by definition, that there's an association between those models. So you should explicitely set that association in your models.
First of all you need to define the association type. In your case it an be either a has_one association in case an article may correnpond to only one favorite or a has_many association if one article may correspond to multiple favorits (multiple favourities can have the save fav_id).
Then you need to set up your association in both the Article and the Favorite models.
In your app/models/article.rb it will go like this:
has_one :favorite, foreign_key: :fav_id
In your app/models/favorite.rb it will go like this:
belongs_to :article, foreign_key: :fav_id
I recommend you though to simplify that by changing the field name from fav_id to article_id so that you can drop the foreign_key parameter in both definitions.
Make sure to read the API docs for more information on working with associations.
After you set up that association, you can easily use it in the controller.
def show
#favorite = Favorite.find params[:id]
#article = #favorite.article
end
And then just use link_to #article.title, #article in your view.
I cant clearly understand what you are going to get, but this:
`
def show
#favorite = Favorite.find(params[:id])
#article = Article.find(params[:id])
end
Is completely wrong. You are trying to use same :id parameter to find either Article and Favorite. I don`t think this is behavior you are waiting for.
The Favorite has an attribute of .fav_id
Also, why Favorite model has fav_id attribute? Is it Single Table Inheritance? Or what? Rails way would be, for example, Favorite to have article_id.
Within a simple CRUD rails app i'm working on, i'm using a comments gem called Opinio
The model that is commentable is called posts. Within my view I am trying to output the comments count per post:
<%= post.comments.size %>
This is only yielding the number of parent comments. It is not including the replies. How do I get the total number of comments per post, including the replies? Where is this gem storing the comment replies?
Any help would be much appreciated!
I've browsed around the source code and apparently comments may be children of other comments but only up to one level: e.g. comment1 belongs to post1 and comment2 belongs to comment1 but comment3 can not belong to comment2 (because the parent of comment2 is already another Comment).
So the query you need to make sounds like this: fetch all parent comments of post1, then fetch all child comments of the previous parent comments, then merge results. Something like:
class Post
# This returns an Array of Comment objects. If you intend to use
# more than the #count method on the result, you should consider
# adding some sort of ordering on the two queries.
def all_comments
parent_comments = Comment.where(:commentable_id => id)
child_comments = Comment.where(:commentable_id => parent_comments.map(&:id))
parent_comments + child_comments
end
end
post = Post.find(1)
post.all_comments.count # The number of all (parent & child) comments of post
If your comment class name is not Comment feel free to change it to whatever (same for Post).
You could try to retrieve the total number of comments on an individual post by using:
#post = Post.find(params[:id])in the controller and
<%= post.comments.count %>in the view.
To get all comments posted you could try
#posts = Post.all in the controller and
<%= #posts.commments.count %> in the view.
The comment replies will be stored in the database. Check the \db\migrate\ folder in your app and you should see the comments migration. If you look in the \db\ folder you should notice a development.sqlite3 database. Opening this using a database viewer will allow you to see the stored data.
Update 1
After reading through the Github repo I spotted a couple of things. Firstly I assume you are using Rails 3? (otherwise the engine doesn't work)
To display comments for a specific item opinio provides a method:
opinio_identifier do |params|
next Review.find(params[:review_id]) if params[:review_id]
next Product.find(params[:product_id]) if params[:product_id]
end
On this method you receive the params variable and you tell the engine who owns the comments from that page. This allows you to use routes like:
/products/1/comments
/products/1/reviews/1/
Ttwo customizations are only made through the opinio initializer, and they are the accept_replies which defaults to true and strip_html_tags_on_save which also defaults to true. You should be able to find this in the commentable model.
Maybe you could try this:
<%= Opinio.model_name.comments.count %> would change to
<%= Opinio.post.comments.count %>
I have a model posts, which belongs_to category (which uses friendly_id). Now i want to list all Posts in an Category. To get the index page i want to use a link like: http://mysite/posts/category/_category_slug_, for that i made the following route:
match 'posts/category/:category/' => 'posts#index'
And in my post controller i got:
def index
if params[:category]
#posts = Post.all(:joins => :category, :conditions => {"categories.cached_slug" => params[:category]})
else
#posts = Post.all.reverse
end
...
It works like it should, but i dont think its the friedndly_id way to do it.
Is there a better way to achive this? thanks for your help.
FriendlyId adds the ability to do a find on a model using the slug, and is smart enough to check the cached_slug column first.
You can achieve the same result by performing a find on the Category model first then getting all the posts.
This assumes there is a has_many and belongs_to association in place with the referencing ID columns (or HABTM)
def index
if params[:category]
#posts = Category.find(params[:category]).posts
else
#posts = Post.all.reverse
end
...
Since you're passing in a category param (being friendly_id), it makes sense to reference it via the Category model.
-- more info added --
ALSO: Historical finds will still work ..
So, if you have generated a new slug by renaming a category, the old url will behave correctly (great if you're avoiding 404's)
Right now my Posts model has_many :tags, :through => :tag_joins
When I add tags, while creating a post, the tag_join records are automatically created.
Now here is what I'm trying to accomplish: While viewing the show view of posts I want to be able to add a new tag.
I tried #post.tag = Tag.new didn't work (returns a "nomethoderror" for tag=)
So I'm trying to figure out how I can add tags and still create those joins automatically.
I am using accepts_nested_attributes etc.
UPDATE: I originally asked how to do this on the index view, but I have changed it to the show view - because I expect it to be a little easier.
You're not too far off with #posts.tags = Tag.new. Here's a couple of ways to do it;
#post.tags << Tag.create(params[:tag])
#post.tags.create params[:tag]
I see a couple of approaches to this problem.. One is to pass through the id of the post with the tag form using either a hidden_field or by using nested routes for tags. Then you can use that in the controller to retrieve the post and use a syntax similar to above.
While that would work, the problem is that it's a bit ugly.. It means your tag controller would be dealing with finding a post (which isn't necessarily wrong, but it shouldn't need to worry about posts. Unless tags can only be associated with posts, that is).
The more graceful way of dealing with it would be to make the form you're showing be a form for the post instance, not a tag. Then you could use nested attributes to create the tag as part of a post.
Take a look at the build_xxx or create_xxx methods that the association (belongs_to, has_many etc) add to the models. You need to create your tag through the post for rails to 'connect' it automatically.
The key observation here is the difference between .new and .create. For my Devour.space application, I was running into the same issue. If you create the object in memory using:
tag = #post.tags.new(tag_params)
tag.save
There will be no tag_joins entry saved to the database. #post.tags will not return your new tag. You must use .create at the moment of instantiation or the association will not be recorded in the JOIN table:
tag = #post.tags.create(tag_params)
#post.tags.last # tag
In my situation, this required a change in how my create action handled requests and errors:
has_many :deck_shares
has_many :decks, through: :deck_shares
....
deck = current_user.decks.new(deck_params)
if deck.save # Does not create entry in DeckShares table
render json: deck
else
render json: deck.errors, as: :unprocessable_entity
end
This became:
begin
deck = current_user.decks.create(deck_params) # creates DeckShare
rescue Exception => e
render json: e, as: :unprocessable_entity
end
render json: deck unless e
Hey I hope you can give me advise on my problem. I got a Course Model, a CourseEnrollment Model and a User Model. In the Course index view it lists all the courses. I got a link_to to join a course. I want to create a new entry on the course_enrollments table.
link_to 'join', current_user.course_enrollments.create(:course_id => course,:user_id => current_user)
In my course_enrollments_controller I have:
def create
#course_enrollment = CourseEnrollment.new(params[:course_id, :user_id])
end
thx for your time
Seems like you are really mixing things up here. You are linking to the return value of the create method of a model but I guess you want to link to the "create" action of a controller.
Maybe you should (re-?) read a guide or introduction on Rails first.