Follow and unfollow an article - ruby-on-rails

I am trying to create an app where a user can follow or unfollow an article. To do that, I created three models, Customer, Article and Pin.
These are the relationships:
Customer
has_many articles
has_many pins
Article
has_many pins
belongs_to customer
Pins
belongs_to customer
belongs_to article
I believe a Pin must be nested within an Article. My route.rb look like this:
resources :articles do
resources :pins, :only => [:create, :destroy]
end
end
In article#index I have a form for creating or destroying the relationships:
# To create
<%= form_for [article, current_customer.pins.new] do |f| %>
<%= f.submit "Pin" %>
<% end %>
# To destroy which doesn't work because i guess you can't do the form like that
<%= form_for [article, current_customer.pins.destroy] do |f| %>
<%= f.submit "Pin" %>
<% end %>
Here are the corresponding controller actions:
def create
#article = Article.find(params[:article_id])
#pin = #article.pins.build(params[:pin])
#pin.customer = current_customer
respond_to do |format|
if #pin.save
format.html { redirect_to #pin, notice: 'Pin created' }
else
format.html { redirect_to root_url }
end
end
end
def destroy
#article = Article.find(params[:article_id])
#pin = #article.pins.find(params[:id])
#pin.destroy
respond_to do |format|
format.html { redirect_to root_url }
end
end
Now here my two questions:
How do I create a form that would delete the current relationship?
In my form I only want to show one of the buttons. How can I conditionally display the correct button?

You don't need a form to delete the relationship, links will do fine. I assume you'll be iterating through your articles in the index view -- if so, how about something like this?
<% #articles.each do |article| %>
...
<% if (pin = current_customer.pins.find_by_article(article)) %>
<%= link_to 'Unfollow', articles_pin_path(article, pin), :confirm => "Are you sure you want to unfollow this article?", :method => :delete %>
<% else %>
<%= link_to 'Follow', articles_pins_path(article), :method => :post %>
<% end %>
<% end %>
One caveat about using link_to for creating/destroying records is that if javascript is disabled, they will fall back to using GET rather than POST/DELETE. See the documentation for details.

Related

Same redirect_to route that worked for post does not work for comment (same url)

I have the following post controller:
def create
#book = Book.find(params[:book_id])
#post = #book.posts.create(post_params)
if #post.save
redirect_to book_path(#book), notice: "Success!~"
else
redirect_to book_path(#book), alert: "Failure!"
end
end
Exactly same redirect_to is used for comments. Comment creation form and list are on the same url as the post creation form and list (which is show.html.erb for book)
comments controller create:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(comment_params)
#comment.user_id = current_user.id
if #comment.save
redirect_to book_path(#book), notice: "Success!~"
else
redirect_to book_path(#book), alert: "Failure!"
end
end
But when I create a comment, this error shows: No route matches {:action=>"show", :controller=>"books", :id=>nil}, missing required keys: [:id]. The comment is created and saved in the database.
I have tried book and book.id instead of #book. None worked. (interestingly, from books list to show.html.erb, I can only go there by book_path(book.id) and not book_path(#book)).
Here's my book show action, and below it is my show.html.erb for book.
#book = Book.find(params[:id])
#post = #book.posts.new
#comment = Comment.new
show.html.erb:
<%= form_for([#book, #book.posts.build]) do |form| %>
<p>
<%= form.text_area :text %>
</p>
<p>
<%= form.submit "Post"%>
</p>
<% end %>
<% #book.posts.each do |post| %>
<p>
<%= #book.title %>
<%= post.text %>
</p>
<%= form_for(post.comments.build, url: "/posts/#{post.id}/comments") do |form| %>
<p>
<%= form.text_area :text %>
</p>
<p>
<%= form.submit "Post comment"%>
</p>
<% end %>
<% end %>
routes:
resources :users do
resources :books, shallow: true
end
resources :books do
resources :posts, shallow: true
end
resources :posts do
resources :comments, shallow: true
end
The error says it all, you are not supplying an id for the book. Notice how the show route is something like /books/2 where 2 is the id? That number is not getting supplied in the comments controller. It looks to me like comments belong to a post which belongs to a book, so this should solve the issue for you.
if #comment.save
redirect_to book_path(#post.book.id), notice: "Success!~"
else
redirect_to book_path(#post.book.id), alert: "Failure!"
end
In your code you are using #book but it does not look like you ever set that variable with any values the way you do in the create method, so there is no id value there, make sense?

Rails 4. Set up comment editing functionality with nested resources

I have the following models: User, Product and Comment. The user can add, edit and delete comments to the product. I've successfully set up adding and deleting functionality and now I'm struggling with editing, for some reason, it caused me a number of difficulties.
My current code returns this error when I click on the edit comment link:
NoMethodError at /products/800/comments/8/edit
undefined method `comments' for nil:NilClass
Here's how my comment model looks like:
# id :integer not null, primary key
# body :text
# created_at :datetime not null
# updated_at :datetime not null
# user_id :integer
# product_id :integer
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :product
# validations ...
end
In User model I have has_many :comments and in Product - has_many :comments, dependent: :destroy.
In my routes.rb file I have the following nested resources:
resources :products, only: [:show] do
resources :comments, only: [:create, :edit, :update, :destroy]
end
My ProductsController has the only method show and nothing else, and here's how it looks like:
def show
product = Product.find(params[:id])
photos = ProductsPhoto.where(product: product)
case product.products_category.name
when 'Violin'
#product = [product, Violin.where(product: product).first, photos]
when 'Guitar'
#product = [product, Guitar.where(product: product).first, photos]
when 'Saxophone'
#product = [product, Saxophone.where(product: product).first, photos]
when 'Piano'
#product = [product, Piano.where(product: product).first, photos]
end
#comment = Comment.new
#comments = Comment.where(product_id: product.id).order('created_at DESC')
end
And now here's my CommentsController, which has create, edit, update and destroy:
class CommentsController < ApplicationController
def create
#product = Product.find(params[:product_id])
#comment = #product.comments.create(comment_params)
#comment.user_id = current_user.id
if #comment.save
redirect_to #product, notice: 'Comment Created!'
else
redirect_to #product, notice: 'Something went wrong...'
end
end
def show
end
def edit
#product = Product.find(params[:product_id])
#comment = #product.comments.find(params[:id])
end
def update
#product = Product.find(params[:product_id])
#comment = #product.comments.find(params[:id])
respond_to do |format|
if #comment.update_attributes(comment_params)
format.html do
redirect_to [#comment.product, #comment], notice: 'Comment Updated!'
end
else
format.html { render action: 'edit', notice: 'Something went wrong...' }
end
end
end
def destroy
#product = Product.find(params[:product_id])
#comment = #product.comments.find(params[:id])
#comment.destroy!
redirect_to #product, notice: 'Comment Deleted!'
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
My _form view is located at views/products/_form.html.erb and looks like this:
<%= simple_form_for([#product[0], #product[0].comments.build]) do |f| %>
<%= f.error_notification %>
<%= f.input :body, required: true, placeholder: 'Type in your comment...', input_html: { class: 'form-control'}, label: false %>
<%= f.button :submit, class: 'btn btn-primary btn-block' %>
<% end %>
And in the views/products/show.html.erb I render the partial of comments and there are the links for destroying and editing the comment, they look like this:
<%= link_to edit_product_comment_path(comment.product, comment) do %>
<span class="glyphicon glyphicon-pencil"></span>
<% end %>
<%= link_to [comment.product, comment], method: :delete do %>
<span class="glyphicon glyphicon-remove"></span>
<% end %>
the delete link works fine and the edit link doesn't.
Maybe I've mistaken with routes, here're the routes for comments:
product_comments POST /products/:product_id/comments(.:format) comments#create
edit_product_comment GET /products/:product_id/comments/:id/edit(.:format) comments#edit
product_comment PATCH /products/:product_id/comments/:id(.:format) comments#update
PUT /products/:product_id/comments/:id(.:format) comments#update
DELETE /products/:product_id/comments/:id(.:format) comments#destroy
In my views/comments/edit.html.erb I render the same form:
<%= render 'products/form' %>
however, when I click on the edit link, I get the following error:
NoMethodError at /products/800/comments/8/edit
undefined method `comments' for nil:NilClass
at the very first line of the _form.html.erb.
I hope I've provided enough information to describe the problem.
So, could you please help me with resolving that issue with comments editing?
It's too early this morning...
This is not correct :
<%= simple_form_for([#product, #product.comments.build]) do |f| %>
Try :
<%= simple_form_for [#comment.product_id, #comment], url: product_comment_path(#comment.product_id, #comment) do |f| %>
Added section
(All of this is assuming that we are working with a single #comment, not the #comments collection. Based on your comments about the delete working.)
To reply to your comment, yes, absolutely you do need separate forms for "new" versus "edit" action for your child table, comments.
Form "new" naming convention is comments/_form.html.erb, the "edit" form would be comments/_form_edit.html.erb,
I would open the "new" action form for comments...
<%= simple_form_for([:product, #comment]) do |f| %>
And the "edit" action...
<%= simple_form_for [#comment.product_id, #comment], url: product_comment_path(#comment.product_id, #comment) do |f| %>
Sorry I forgot the URL on the previous version, I have updated the code snippet above to reflect this change as well. Caveat: There may be other ways to construct this link, but this is how I have implemented the case of having a product (parent) with reviews/comments (many children).
Nested attributes
In further response to your comment, I believe some of what you're looking for could be achieved by using nested attributes in forms. This is another topic. I would get the new/edit form simple cases working and then add complexity.

Update database using 'update_attributes' through 'has_many'

I'm having a problem getting my first app (I'm a total newbie) to save a new associated record. I have two models (users and pictures) with a has_many/belongs_to association. I have set up the userController so that it can create a new picture as below:
def new_picture
#user = User.find(current_user.id)
#picture = #user.pictures.build
end
def create_picture
#user = User.find(params[:id])
#picture = #user.pictures.build(params[:picture])
if #picture.save
flash[:notice] = "Your picture was successfully added."
redirect_to :action => 'show', :id => #user.id
else
render :template => "new_picture"
end
end
and I use
<%= link_to("add picture", :action => 'new_picture', :id => #user.id) if current_user %>
to add a new one. But I'd also like to be able to edit. So I updated the usercontroller with some new code:
def edit_picture
#user = User.find(current_user.id)
#picture = #user.pictures.find(params[:id])
end
# When the user clicks the save button update record
def update_picture
#user = User.find(current_user.id)
#picture = #user.pictures.find(params[:picture])
respond_to do |format|
if #picture.update_attributes(params[:picture])
flash[:notice] = "Your picture was successfully updated."
redirect_to :action => 'show', :id => #user.id
else
render :template => "new_picture"
end
end
end
and added the edit link to show.erb:
<%= link_to("edit picture", :action => 'edit_picture', :id => picture.id) if current_user %>
It loads the edit form fine, with the data all in the right place, but on save all it's doing is giving me the error 'ArgumentError in UsersController#update_picture' with a bunch of Unknown key(s) from my pictures table.
Could somebody explain why? I feel like there is one piece of the jigsaw I haven't quite understood here....
Thanks in advance!
UPDATE: View code is as follows:
<h1>New picture for <%= #user.name %></h1>
<% form_for :picture, #picture, :html => { :multipart => true }, :url => {:action => 'update_picture', :id => #user.id} do |f| %>
Can't seem to see your problem in the view code, however you can do the same thing more elegantly (RESTful) as a nested route. That way you might be able to see the problem more clearly.
config/routes.rb:
resources :users do
member do
resources :pictures
end
end
app/controllers/pictures_controller.rb:
class PicturesController < ApplicationController
before_filter :find_picture, :only => [:edit, :update]
def edit
end
def update
if #picture.update_attributes params[:picture]
flash[:notice] = "Your picture was successfully updated."
redirect_to user_path(current_user)
else
render :edit
end
end
protected
def find_picture
#picture = current_user.pictures.find params[:id]
end
end
app/views/pictures/edit.html.erb:
<%= form_for [current_user, #picture] do |f| %>
<!-- some stuff -->
<% end %>
and to link to your edit form:
<%= link_to_if current_user, 'edit picture',
edit_user_picture_path(:user => current_user, :id => picture) %>
I suggest adding 'accepts_nested_attributes_for :pictures to the user model, and then do
<%= form_for #user do |form| %>
.. user fields
<%= form.fields_for :pictures do |picture_form| %>
.. picture fields
<% end %>
<%= form.submit %>
<% end %>
in the view.
Another option is to create a new controller for the pictures. That may be simpler.

Why can't I build more than one nested attribute here?

this is my form code:
<%= simple_form_for setup_video(#video) do |f| %>
<% f.fields_for :comment_titles do |t| %>
<%= t.input :title, :label => "Comment Title:" %>
<%= t.button :submit, :value => 'Add', :id => 'add_comment_title' %>
<div class='hint'>Let your listeners know what comments you want by adding a guiding title for them. Pose a question, ask for feedback, or anything else!</div>
<% end %>
<% end %>
I have has_many :comment_titles and accepts_nested_attributes_for :comment_titles, :comments in my model. when I create a new comment_title in the form, the old one is replaced. I want an additional one to be built. How can I do this?
Here are the video controller actions:
def new
#video = Video.new
respond_to do |format|
format.js do
render_to_facebox(:partial => 'add_video')
end
end
end
def create
#video = current_user.videos.new(params[:video])
respond_to do |format|
if #video.save
format.html { redirect_to(#video) }
else
format.html { render :action => "new" }
end
end
end
I think this is actually what is needed:
def update
#video = current_user.videos.find(params[:id])
respond_to do |format|
if #video.update_attributes(params[:video])
format.html { redirect_to(#video) }
format.js
else
format.html { render :action => "edit" }
end
end
end
The edit action here will provide a form which will allow you to edit the existing record as well as its nested attributes. This is why it's replacing the existing object.
If you only want people to add new comment titles then I would recommend building a new object in your edit action like this:
def edit
video = current_user.videos.find(params[:id])
video.comment_titles.build
end
Then this will be available as an additional row in your fields_for call. To only make this show new objects:
<% f.fields_for :comment_titles do |t| %>
<% if t.object.new_record? %>
# stuff goes here
<% end %>
<% end %>
However this restricts people to being able to only add new items in an edit action, which may seen counter-intuitive to some users.

Rails Associations (belongs_to, has_many) can't save 2 ids in table with a create method (user, post, comment)

Trying to write a basic "blog-like" app in rails 3, I'm stuck with associations. I need the create method save the post_id as well as the user_id in the comment table (which I need in order to retrive all comments written by a user in order to display it)
The app has users (authentication - devise), posts (posted by users - but I'm not sure it matters in my case) and comments (on the posts, posted by users).
the comment table has a post_id, a body, and also a user_id
Associations:
has_many :comments (In the Post model)
belongs_to :post (In the Comment model)
belongs_to :user (In the Comment model)
has_many :comments (In the User model)
the routes:
resources :posts do
resources :comments
end
resources :users do
resources :comments
end
The comment post form displayed on the posts show view: (posts/show.html.erb)
<% form_for [#post, Comment.new] do |f| %>
<%= f.label :body %>
<%= f.text_area :body %>
<%= f.submit %>
<% end %>
and finally, the create method in the comments controller:
A.) If I write this a post_id is written in the database
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create!(params[:comment])
redirect_to #post
end
B.) If I write this a user_id is written...
def create
#user = current_user
#comment = #user.comments.create!(params[:comment])
redirect_to #post
end
I tried:
#comment = #post.comments.create!(params[:comment].merge(:user => current_user))
But it doesn't work.. How can I write a method which save the user_id and the post_id ? Did I have also to do some change in the comment post form (something like <% form_for [#post, #user, Comment.new] do |f| %> ?)
Thank you!
To set up something very similar, I've used the following form:
<%= form_for [:place, #comment] do |f| %>
#form fields here
<%= end %>
Then in the comments controller:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(params[:comment])
#comment.user = User.find(current_user.id)
respond_to do |format|
if #comment.save
format.html { redirect_to(#comment.post, :notice => 'Comment was successfully created.') }
else
format.html { render :action => "new" }
end
end
end
That should build the associations properly hopefully! Just as an aside, do you mean for comments to be nested under :users in your routes? If you just want to display all the user's comments on a profile page, you could do something like:
<p>
<b>Comments</b>
<% if #user.comments.empty? %>
No comments to display yet...
<% else %>
<% #user.comments.each do |comment| %>
<p>
<%= link_to "#{comment.post.title}", post_path(comment.post_id) %>, <%= comment.created_at %>
<%= simple_format comment.content %>
</p>
<% end %>
<% end %>
</p>
Hope some of that helps!

Resources