Rails 3: Ajax nested resource delete - ruby-on-rails-3

I have a nested resource model in my Rails 3 app. It is the standard blog app with posts and comments. I have just started using jQuery etc to make my app more dynamic, I am now struggling to remove comments in the nested model with the link_to helper.
Comments Model
class Comment < ActiveRecord::Base
belongs_to :post
end
Post Model
class Post < ActiveRecord::Base
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
In my Posts/show.html.erb I have the following bloc that displays all the comments with a link_to helper to delete the comments. This works with HTML but when I added :remote => true, it deleted the parent post instead of the comment! How can I set it up so it deletes only the comment?
<% #post.comments.each do |comment| %>
<%= comment.body %>
<%= link_to "Approve", [#post,comment], :method =>:put, :remote=>true %>
<%= link_to "Delete", [#post,comment], :method =>:delete, :remote=>true %>
<%end%>
Thanks,

I think you want to have this in your delete action of your Comment class:
def delete
...
respond_to do |format|
format.html
format.js
end
end
Then, in your views/comments directory, you should have a file named delete.js.erb that does your jQuery DOM manipulation (finds the particular comment that you clicked the delete link of and remove it.
Then, your link_to for the delete method in your Posts/show.html.erb file, you can specify the controller and action, and also pass in any data you might need (the parent post so you can refer to it in your jQuery). You can refer to the first examples section of this site for the syntax on specifying a particular controller and action for a link_to helper here.

Just check the corresponding action in the controller.
If there is a render or redirect_to just delete it
it works for me (Rails 4)

Related

How do I create a form that will update the value of a field for selected records?

I am using a link from an index page that has a group of nested records (row) that I need to update all at once. The link goes to an edit action that I need to make update the attributes of nested records (prisms).
I tried using the simple_form gem methods for nested models. It gives me a field for all of the objects, when I only want one field to enter a value to them all. The builder from that looks usable, but I don't know how to use it to update the fields. Either way, the form isn't right.
I have tried every variation of form_for and fields_for I could find on Google to develop the edit form. It looks like I'm the only one on Earth trying to solve this problem.
This is how I have my routes set up:
resources :gardens, shallow: true do
resources :prisms
resources :rows
Here is how my garden model is now:
class Garden < ApplicationRecord
mount_uploader :picture, ImageUploader
belongs_to :user
has_one :photo
has_many :rows, :dependent => :destroy
has_many :prisms
geocoded_by :address
after_validation :geocode
after_commit :populate_garden!
def id
self[:id]
end
def populate_garden!
# row 0
(0..length-1).each do |i|
Row.create(row_num: i, garden_id: id)
end
end
end
The garden model creates my rows through the populate_garden! method.
Here is the row model:
class Row < ApplicationRecord
belongs_to :garden
has_many :prisms, :dependent => :destroy
accepts_nested_attributes_for :prisms
after_commit :populate_rows
def id
self[:id]
end
def populate_rows
# row 0
(0..garden.width-1).each do |i|
Prism.create(:row_id => self.id, :row_num => self.row_num, :col_num => i, :garden_id => self.garden_id)
end
end
end
The row model creates prisms in the populate_rows method.
Here is the prism model:
class Prism < ApplicationRecord
belongs_to :row
belongs_to :garden
include RankedModel
ranks :column_order
end
Here is the table from my index.html.erb that I click to open the edit action.
<table>
<% #rows.each_with_index do |gardenrow, index| %>
<% #rows.select { | row | row.row_num == index}.each do |row| %>
<td class="prism-cols">
<%= link_to 'Edit Row', edit_row_path(row), class:"dark-link" %>
<br /><i class="fas fa-arrow-down"></i>
</td>
<% end %>
<% end %>
</table>
The row passes nicely into the edit action, and I currently have this incorrect form:
<h1>The row brought into this form page is: </h1>
<%= #row.inspect %>
<div class="container">
<%= simple_form_for #row do |m| %>
<%= m.simple_fields_for :prisms do |p| %>
<%= p.input :crop_name %>
<% end %>
<%= m.button :submit %>
<% end %>
</div>
The rows_controller update method looks like this:
def update
#row = Row.find(params[:row_id])
#row.prisms.build
redirect_to root_path
end
I need one form field for crop_name that will change all of the prisms in the selected row with a single submit. I don't have any problems updating one prism at a time through an edit action on the prism. The difficulty I'm having is working through the nesting of prisms inside of a specific row.
With the help of my mentor below I was able to develop a form that works with the controller to make this work. Here is the updated code for later use with this type of problem.
Here is the form data:
<%= form_tag({controller: "rows", action: "update"}, method: "patch") %>
<%= label_tag(:crop_name, "Crop Name") %>
<%= text_field_tag(:crop_name) %>
<%= hidden_field_tag(:row_id, #row.id) %>
<%= submit_tag("submit") %>
Here is the controller update method:
def update
#row = Row.find(params[:id])
#garden = Garden.find_by_id(:garden_id)
#row.prisms.each do |p|
p.crop_name = params[:crop_name]
p.save!
end
redirect_to :controller => 'gardens', :action => 'show', id: #row.garden_id
end
Thanks for the help. I don't think I could have figured this out from the documentation alone.
If I'm understanding correctly, I think simple_form may be limiting you. A basic ruby form may do what you want. I'm not 100% sure what the best way is to do a simple_form on nested fields but this stackoverflow answer may be able to help more.
Using a basic ruby form
You want a form that has one field. When submitted, it will take the value from the submitted form and update that field for all prisms of that row. I would recommend digging more into the basics of ruby forms for this kind of scenario and then do something like this.
// html.erb
<%= form_tag({controller: "rows", action: "update_prism_crop_name"}, method: "post", class: "nifty_form") %>
<%= label_tag(:crop_name, "Crop name") %>
<%= text_field_tag(:crop_name) %>
<%= hidden_field_tag(:row_id, #row.id) %>
<%= submit_tag("Submit") %>
<% end %>
// rows_controller
def update_prism_crop_name
#row = Row.find(params[:row_id])
#row.prisms.each do |prism|
prism.crop_name = params[:crop_name]
prism.save!
end
# other redirect stuff
end
The form_tag explicitly calls out an action but I have to imagine that you'll need to build a route for this custom action as well.
I haven't tested any of this and I'm a bit rusty in rails but I believe something like this would work.

Rails Jquery How to submit multiple forms via ajax

I have a form with a has_many association and I would like to submit multiple forms via ajax so the parent form's show page can be updated dynamically.
I have the following models (they are in mongoid but i don't think it matters between mongoid and activerecord):
class User
include Mongoid::Document
include Mongoid::Timestamps
has_many :tasks
end
class Task
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :user, inverse_of: :tasks
end
I have a form which renders n partials of the form:
<%= form_for #task, remote: true do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
for new tasks on the show.html.erb for user via ajax.
How can i write a single submit button on the show.html.erb to submit all of the tasks at once while associating them with the parent user?
Based on the second answer to this question: Rails 3: How to trigger a form submission via javascript?
Say you have a button with an id called #multiSubmit, then you can submit each form when it is clicked using the following JavaScript:
$(function() {
$("#multiSubmit").click( function() {
$('form').each(function(i, item) {
$(item).trigger('submit.rails');
});
});
});

Rails 3 save model and redirect using coffeescript

I have user class (using devise) and my User class has email subscriptions
class User < ActiveRecord::Base
...
has_one :email_sub, :class_name => "Subscriptions::EmailSub", :dependent => :destroy
end
I have a route
match 'profile', :controller => 'users', :action => 'view_profile'
and related controller that calls a specific file
class UsersController < ApplicationController
def profile
#subscriptions_email_sub = current_user.email_sub
end
end
And in the profile.html.erb file have a form (technically in a partial included in the template but I don't think that will make a difference) where the user can use radio buttons to set subscription options (subscribed or unsubscribed). The button part of the form is below:
<%= form_for(#subscriptions_email_sub) do |f| %>
...
<tr>
<td>Announcements</td>
<td><%= f.radio_button :announcements, 'announcements', :checked => #subscriptions_email_sub.announcements %></td>
<td><%= f.radio_button :announcements, 'announcements', :checked => !#subscriptions_email_sub.announcements %></td>
<td></td>
<td>Updates about the website and service</td>
</tr>
<%= f.submit "Update Subscriptions", :id => 'update_subs' %>
<% end %>
I'm trying to add coffeescript so when the user clicks the button it saves the model and notifies the user the settings have been updated (or an alert if there's an error).
(Alternatively I'm fine saving the model and reloading the whole page.)
So far I have in users.js.coffee
$ ->
$('#update_subs').click ->
$('form').submit();
I can't figure out how to get the page to reload. I think the form submit will cause it to use the controller for Subscriptions::EmailSub which will then try to load app/views/subscriptions/email_subs/show.html.erb I suspect I may have to use javascript to save the class and the reload the page.
Thanks for any help.
You don't need that code in users.js.coffee, you can make the form remote:
<%= form_for(#subscriptions_email_sub), :remote => true do |f| %>
This will submit the form asynchronously, expecting a JS response (unless you specify another content-type such as JSON).
You'll need to have a controller action to handle this update, presumably your SubscriptionsController.
It should respond to javascript requests:
class SubscriptionsController < ApplicationController
respond_to :js
...
def update
#subscription = Subscription.find(params[:id])
...
respond_with(#subscription)
end
end
Then you can simply add an update.js.coffee file to your views/subscriptions folder and add any changes you want to make to the DOM in there, just as you would for regular javascript.
# update.js.coffee
alert "Email subscription updated!"
$('form').ignite_fireworks()

routes and CRUD

I have an app (a tutorial) which has Articles and Comments. An Article has_many Comments. A Comment belongs_to an Article. I'm having a problem deleting an Article's Comment. Here are the files in question:
app/views/comments/_comment.html.erb
<%= div_for comment do %>
<h3>
<%= comment.name %> <<%= comment.email %>> said:
<span class='actions'>
<%= link_to 'Delete', [#article, comment], confirm: 'Are you sure?', method: :delete %>
</span>
</h3>
<%= comment.body %>
<% end %>
CommentsController
before_filter :load_article
def create
#comment = #article.comments.new(params[:comment])
if #comment.save
redirect_to #article, :notice => 'Thanks for your comment'
else
redirect_to #article, :alert => 'Unable to add comment'
end
end
def destroy
#comment = #article.comments.find(params[:id])
#comment.destroy
redirect_to #article, :notice => 'Comment deleted'
end
private
def load_article
#article = Article.find(params[:article_id])
end
routes.rb
resources :articles do
resources :comments
end
The problem is when I'm at address localhost:3000/articles/1 and try to delete a comment. Instead of being redirected to the Article show action I get this error at address localhost:3000/articles/1/comments/3:
Unknown action
The action 'show' could not be found for CommentsController
any help greatly appreciated,
thanks,
mike
You have two basic options here, because a link in most browsers can only send a GET request.
First option is to include the java-script default files into the page
<%= javascript_include_tag :defaults %> #this mocks a delete action by modifying the request automatically
Second and much preferable is to use button_to instead. First, there is a logical separation between a link to a place and a button to do something. Delete is definitely an action. Further, buttons aren't followed by spiders so nothing ever gets accidentally called.
<%= button_to 'delete', #comment, :method => :delete %>
========= EDIT FOR COMPLETENESS =======
If you are worried about the links and buttons not looking the same, an easy solution is to us jquery/jquery_ui to style all links and buttons exactly the same.

Issues getting a profile to update and show newly submitted form item

Following up on a previous question, I have a few issues to resolve before I have a comment form showing and submitting securely on my profile. I'm a beginner to programming so thinking across multiple controllers seems to have me lost.
What I'm doing is posting comments in a form, then listing them.
Background: The _comment_form and _comment reside as partials in the Profile about. (My next task is toggling from about to other Profile information, but that's another question altogether.)
Using the help provided in my last question I feel like I'm almost there but am getting an error.
CreateComments migration:
t.integer :profile_id
t.integer :author_id
t.string :body
My Comment model:
class Comment < ActiveRecord::Base
belongs_to :profile
belongs_to :author, :class_name =>"User", :foreign_key => "author_id"
end
CommentsController:
def create
#comment = Comment.new(params[:comment].merge(:author_id => current_user.id))
#comment.save!
redirect_to profile_path(#comment.profile)
end
ProfilesController:
def create
#profile = Profile.new(params[:profile])
if #profile.save
redirect_to profile_path(#profile), :notice => 'User successfully added.'
else
render :action => 'new'
end
end
def show
#user = User.find(params[:id])
#profile = #user.profile
#comment = #profile.comments.new
end
Comment partials inside Profile partial:
<div id="commentEntry">
<%= render :partial => 'comment', :collection => #profile.comments %>
</div>
<div id="newitem">
<%= render :partial => 'comment_form' %>
</div>
Routes.rb:
resources :users do
resources :profiles
end
resources :comments
_comment_form.html.erb:
<%= form_for #comment do |f| %>
<%= f.text_field :body %>
<%= f.submit 'Add new' %>
<% end %>
_comment.html.erb:
<li class="comment" title="<%= #comment.author.profile.first_name %> <%= #comment.author.profile.last_name %>">
<%= #comment.body %>
</li>
So, Issue #1: Wrapping the _comment.html.erb in a loop <% for #comment in #user.profile.comments %> shows the profile but when I try and submit a new comment I get "Unknown action The action 'update' could not be found for CommentsController". If I take away the loop, the profile doesn't show and I get "NoMethodError in Profiles#show undefined method `profile' for nil:NilClass". Can anyone help me out and explain what I'm doing wrong?
Issue #2: I created a sample comment in rails console and when I get the profile to show, the input field for comment :body repopulates with the comment's body. Any ideas on what could be going on?
Short explanation of your problem:
The #comment you're getting in your _comment_form partial is one that's already saved in your database, hence the call to the update action and the body that's already filled.
You're creating the new comment just fine with #comment = #profile.comments.new in your show action, but it gets overridden somewhere else.
You're mentioning that you wrapped the _comment render in a loop with <% for #comment in #user.profile.comments %>, the problem is most likely there.
Fix:
The only thing you should have to change is the _comment partial to (without the for loop that you added):
<li class="comment" title="<%= comment.author.profile.first_name %> <%= comment.author.profile.last_name %>">
<%= comment.body %>
</li>
When you do the render :partial => 'comment', :collection => #profile.comments, rails is smart enough to loop over #profile.comments and give the comment (not #comment) variable to the partial.
How to avoid this the next time:
I'll give you two rules of thumb to avoid getting in this situation:
Try to name your variables more precisely. #new_comment would have been a better name for the variable to store the new comment. #comment is a bit ambigous as you've got a boatload of those in your view.
Avoid creating and modifying instance variables (# variables) in your views, try to do this only in your controller. I'll admit your particular case was a bit harder to detect because of the <% for #comment in #user.profile.comments %>. The view got its name for a good reason, it's only supposed to let you view the data you've defined in your controller.
Hope this helps.