Editing multiple nested records simultaneously in Rails - ruby-on-rails

I'm creating a simple quiz application where a question can have multiple answers. To improve the usability of my app, I want users to be able to edit ALL the answers for a given question in the same form:
I found this great Railscast/Asciicast episode which does almost EXACTLY what I want to do
http://asciicasts.com/episodes/198-edit-multiple-individually
However, there's a catch. My answers model is nested within questions like so:
map.resources :answers, :has_one => :question
map.resources :questions, :has_many => :answers
So when it comes time to define the route and form tag I come a bit unstuck... The tutorial suggests creating 2 new controller methods and defining the routes and form tags as follows:
map.resources :products, :collection => { :edit_individual => :post, :update_individual => :put }
<% form_tag edit_individual_products_path do %>
But this doesn't work in my case as Answers are dependent on Questions... Any ideas on how to translate this tutorial for my nested models?

Working with nested routes looks pretty from some point of view, but always become a bit tricky. In order for this to work you will have to
Fix the routes definition
Adapt the URL generators in all views (like form_for or answers_path)
Routes
First things first: Specifying associations within the routes won't allow you to add custom routes to the second class. I would do something like this:
map.resources :questions do |question|
question.resources :answers, :collection => {
:edit_individual => :post,
:update_individual => :put }
end
Views
It's really important to notice the change in URL generators:
edit_answer_path(#answer) => edit_question_answer_path(#question, #answer)
edit_individual_answer_path(#answer) => edit_individual_question_answer_path(#question, #answer)
I've made a fast adaptation of the Railscasts views:
<!-- views/answers/index.html.erb -->
<% form_tag edit_individual_question_answer_path(#question) do %>
<table>
<tr>
<th></th>
<th>Name</th>
<th>Value</th>
</tr>
<% for answer in #answers %>
<tr>
<td><%= check_box_tag "answer_id_ids[]", answer.id %></td>
<td><%=h answer.name %></td>
<td><%=h answer.value %></td>
<td><%= link_to "Edit", edit_question_answer_path(#question, answer) %></td>
<td><%= link_to "Destroy", question_answer_path(#question, answer), :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<p>
<%= select_tag :field, options_for_select([["All Fields", ""], ["Name", "name"], ["Value", "value"]]) %>
<%= submit_tag "Edit Checked" %>
</p>
<% end %>
<!-- views/answers/edit_individual.html.erb -->
<% form_tag update_individual_question_answers_path, :method => :put do %>
<% for answer in #answers %>
<% fields_for "answers[]", answer do |f| %>
<h2><%=h answer.name %></h2>
<%= render "fields", :f => f %>
<% end %>
<% end %>
<p><%= submit_tag "Submit" %></p>
<% end %>
Extra
As you may have seen, you will require the variable #question within your views, so I would recommend you to have a before_filter in your AnswersController that fetches the question object:
AnswersController
before_filer :get_question
[...]
private
def get_question
# #question will be required by all views
#question = Question.find(params[:question_id])
end
end
Enjoy your nested routes!!

Related

How to use Rails 5.2 form_with to trigger a specific action?

My application needs to duplicate a Skill (from skills index) as many times the user needs it in his cart. So I decided to trigger the add-to-cart method of the skills_controller when the related form, including the number of duplicates and the Skill's id, is submitted. For this purpose, I added counter to the strong parameters of skills_controller.
Unfortunately, I am missing something to correctly setup the form: when submitted, it triggers the create method. Here is the code:
routes.rb extract
resources :skills, :path => "variables" do
resources :values_lists
member do
post :add_to_cart
get :create_values_list
get :upload_values_list
get :remove_values_list
end
collection do
get :index_all
end
end
skills_controller.rb method
def add_to_cart
#template_skill = Skill.find(params[:id])
iterations = params[:skill][:counter].to_i
until iterations == 0
#skill = #template_skill.deep_clone include: [:translations, :values_lists]
#skill.business_object_id = session[:cart_id]
#skill.template_skill_id = #template_skill.id
#skill.code = "#{#template_skill.code}-#{Time.now.strftime("%Y%m%d:%H%M%S")}-#{iterations}"
#skill.is_template = false
#skill.save
iterations -= 1
end
#business_object = BusinessObject.find(session[:cart_id])
redirect_to #business_object, notice: t('SkillAdded2BO') # 'Skill successfully added to business object'
end
index.html.erb table content
<tbody>
<% #skills.each do |skill| %>
<tr data-href="<%= url_for skill %>">
<% if not session[:cart_id].nil? %>
<td>
<%= form_with model: #skill, :action => "add_to_cart", :method => :post, remote: false do |f| %>
<%= f.text_field :counter, value: "1", class: "mat-input-element", autofocus: true %>
<button type="submit" class="mat-icon-button mat-button-base mat-primary add-button" title="<%= t('AddToUsed') %>">
<span class="fa fa-plus"></span>
</button>
<% end %>
</td>
<% end %>
<td class="no-wrap"><%= skill.code %></td>
<td><%= link_to skill.translated_name, skill %></td>
<td><%= link_to translation_for(skill.parent.name_translations), skill.parent %></td>
<td><%= skill.responsible.name %></td>
<td><%= skill.updated_by %></td>
<td class="text-right"><%= format_date(skill.updated_at) %></td>
</tr>
<% end %>
</tbody>
Thanks a lot for your help!
According to this form helpers guide, the syntax you used doesn't exist
form_with model: #model, action: :custom_action
So in this case, you have to specify the url parameter for form_with to make it works.
<%= form_with model: #skill, url: :add_to_cart_skill_path(#skill), method: :post, remote: false do |f| %>

how can i get project_id by remarks in ruby on rails

I have manager remark model that takes input as a remark and decision value and saves it with the project site ID. I have a project site model that takes input as name, date, and file and stores it. Many remarks have a many to one relation with project site ID, and the project site belongs to the manager remark. I want to access the decision attribute boolean value in project site index form, but I am unable to access that boolean value in the index page of the project site. Here is my code of project site and manager remarks model, view and controller-
project site index.html.erb
<table>
<thead>
<tr>
<th>Name</th>
<th>Date</th>
<th>Attendance</th>
<th>Status</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #project_sites.each do |project_site| %>
<tr>
<td><%= project_site.name.titleize %></td>
<td><%= project_site.date %></td>
<td><%= link_to ' View attendance', project_site.file, :class => "fi-page-export-csv" %></td>
<td><%= "here i want to access manager remark decision value" %></td>
<td><%= link_to 'Remark ', project_site %><span>(<%= project_site.manager_remarks.size %>)</span></td>
<td><%= link_to 'Edit', edit_project_site_path(project_site) %></td>
<td><%= link_to 'Destroy', project_site, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
project site controller
def index
#project_sites = ProjectSite.all.order("created_at DESC")
#manager_remark = ManagerRemark.joins(:project_site).where(:project_sites => { :user_id => #user.id })
end
# GET /project_sites/1
# GET /project_sites/1.json
def show
#manager_remark = ManagerRemark.new
#manager_remark.project_site_id = #project_site.id
end
# GET /project_sites/new
def new
#project_site = ProjectSite.new
end
def project_site_params
params.require(:project_site).permit(:name, :date, :file)
end
manager_remark controller
class ManagerRemarksController < ApplicationController
def create
#manager_remark = ManagerRemark.new(remark_params)
#manager_remark.project_site_id = params[:project_site_id]
#manager_remark.save
redirect_to project_site_path(#manager_remark.project_site)
end
def remark_params
params.require(:manager_remark).permit(:remark, :decision)
end
end
manager_remark view form
<%= form_for [ #project_site, #manager_remark ] do |f| %>
<div class="row">
<div class="medium-6 columns">
<%= f.radio_button :decision, true %>
<%= f.label :approve %>
<%= f.radio_button :decision, false %>
<%= f.label :reject %>
</div>
<br>
<br>
<div class="medium-6 cloumns">
<%= f.label :remark %><br/>
<%= f.text_area :remark %>
</div>
</div>
<div>
<%= f.submit 'Submit', :class => 'button primary' %>
</div>
<% end %>
routes.rb
Rails.application.routes.draw do
root to: 'home#index'
devise_for :users
resources :project_sites do
resources :manager_remarks
end
get '/project_manager_level_two' => 'project_manager_level_two#index'
get '/project_managers' => 'project_managers#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
If I understand correctly, you have a ProjectSite that contains a ManagerRemark with a decision, right? If that's the case, the simple answer is:
<%= project_site.ManagerRemark.decision %>
If you are saying that each ProjectSite has many ManagerRemarks, you'll want to place the above inside a loop, like so:
<% project_site.manager_remarks.each do |manager_remark| %>
<%= manager_remark.decision %><br/>
<% end %>
This assumes that your models are correctly configured to recognize these relationships. The above may also be optimized by adding an include clause to your fetch inside the controller and there's no need to fetch the ManagerRemark objects separately. Therefore, you'd probably want something like:
def index
#project_sites = ProjectSite.all.includes( :manager_remark ).order("created_at DESC")
end

UrlGenerationError in Rails App

I've searched this site and asked a couple other programmers and can't figure out what the problem is with my code. I'm a beginner to Rails, so I'm still trying to figure everything out.
I'm getting this error:
ActionController::UrlGenerationError in Lists#show
No route matches {:action=>"toggle_completed", :controller=>"tasks", :id=>nil, :list_id=>3} missing required keys: [:id]
Here is the code it's referring to on the Lists#show page:
<p id="notice"><%= notice %></p>
<h3>add a task</h3>
 <%= render 'tasks/form', task: Task.new() %>
<p>
<strong>List Name:</strong>
<%= #list.name %><p></p>
<% #list.tasks.each do |task| %>
<td><%= task.id %></td>
<td><%= task.name %></td>
<td><%= task.completed %></td>
<td><%= link_to('Toggle', toggle_completed_list_task_path(:list_id => #list.id,
:id => task.id), :method => :put ) %></td><br />
<% end %>
</p>
My Routes:
resources :lists do
resources :tasks do
member do
put :toggle_completed
end
end
end
Tasks Controller:
def toggle_completed
#task = Task.find(params[:id])
#task.completed = !#task.completed
#task.save
end
List has_many: tasks
and
Task belongs_to: list
I've experimented a bit and on my Lists#show page, if I add this line:
<%= task.id %>
The correct value appears on the page, so I'm not sure why it's coming up nil. I've searched the site and haven't found anything that really discusses this exact issue. Thanks!
create a separate routes for toggle_completed ex-
put 'list/task/:id', to: 'tasks#toggle_completed', as: :list_task_completed
and
<%= link_to('Toggle', list_task_completed_path(:id => task.id), :method => :put ) %>

Passing parameters using link_to

I'm trying to pass parameters using link_to with ruby on rails, but it says the id parameter I'm sending is null.
code from where I'm sending the id.
<% #conference.papers.each do |paper| %>
<tr>
<td><%= paper.title %></td>
<td><%= paper.author %></td>
<td><%= link_to "Download Paper", paper.attachment_url %></td>
<td><%= link_to 'Reviews', paper %></td>
<% if (paper.accepted) %>
<td><%= "Accepted" %></td>
<% else %>
<td><%= "Not accepted" %></td>
<% end %>
<% if (#state1 && paper.accepted == false) %>
<td><%= button_to "Accept", accept_paper_path(id: paper.id), class: "btn btn-danger", data: { confirm: "Are you sure that you wish to accept #{paper.title}?"} %></td>
<% end %>
<% if (#state2) %>
<% session["a"] = paper.id %>
<td><%= link_to "Review paper", new_review_path(id: paper) %></td>
<% end %>
</tr>
<% end %>
code for the review controller
def new
#paper = Paper.find_by_id(params[:id])
#review = Review.new()
end
You missed .id in
link_to "Review paper", new_review_path(id: paper.id)
But it is not a good solution. If your Paper model has_many :reviews, it would be better to nest reviews routes in paper's ones. Like this:
# config/routes.rb
resources :papers do
resources :reviews
end
And so, your link_to will look like:
link_to "Review paper", new_paper_review_path(paper)
which will generate
/papers/:paper_id/reviews/new
You can learn more about Rails routing here.
Lets start by setting up the routes properly:
resouces :papers do
member do
patch :accept
end
end
This will let you accept a review by PATCH /papers/:id. To create the button use:
<%= button_to accept_paper_path(paper), method: :patch %>
Note that this should use the PATCH or PUT http method - not GET since it is a non-idempotent action.
Note that you can just pass the model instead of doing accept_paper_path(id: model) or accept_paper_path(id: model.id).
For reviews you will want to create what is called a nested resource:
resouces :papers do
member do
patch :accept
end
resources :reviews, only: [:new, :create]
end
This gives you the route /papers/:paper_id/reviews/new.
<%= link_to "Review paper", new_paper_review_path(paper) %>
To set the form to create a new review to the use correct path use an array containing the parent and child:
<%= form_for([#paper, #review]) %>

How do I create 'remove tag' links with Acts_As_Taggable_On in Rails?

I'm creating a simple project tagging application using the Act_As_Taggable_On gem.
Adding projects, and adding tags (in my case 'types' / 'type_list') to each project works great. Where I'm stuck is how to remove individual tags using Act_As_Taggable_On. What I would like is to be able to click the 'x' text link next to each tag (see link) to remove the tag from that projects type_list.
I've searched the documentation and found a method along the lines of:
project.type_list.remove("your tag")
But what I need help with is how to call the remove method on the specific tag, especially since the whole thing is being iterated with .each do
My controller and model code is pretty minimal and standard - based on Act_As_Taggable_On docs. Here is my view code for generating the layout above:
<h1><%= #title %></h1>
<div class="column-left">
<% #projects.each do |project| %>
<div class="p_wrapper">
<table>
<tr>
<td><div class="project p_name"><%= project.name %></div></td>
<td><div class="p_link"><%= link_to 'Edit', edit_project_path(project) %></div></td>
<td><div class="p_link"><%= link_to 'Nuke', project, :confirm => 'Are you sure?', :method => :delete %></div></td>
</tr>
</table>
<table>
<tr>
<td>
<% project.type_list.each do |tag|%>
<div class="p_tag">
<%= tag %>
<%= link_to "x", # %> <!-- THIS IS THE PART I'M STUCK ON -->
</div>
<% end %>
</td>
</tr>
</table>
<table>
<tr>
<td>
<%= form_for(project) do |f| %>
<%= f.text_field :inject_tags %>
<%= f.submit "Add Tag" %>
<% end %>
</td>
</tr>
</table>
</div>
<% end %>
<br />
<%= link_to 'Add new project', new_project_path %>
</div>
Can anyone point me in the right direction? Am I implementing this correctly to be able to actually remove tags as described?
Thanks guys!
I would simply add a new method to your projects controller, like so:
def remove_tag
Project.find(params[:id]).type_list.remove(params[:tag])
end
And in your routes file
resources :projects do
member do
put 'remove_tag', :as => :remove_tag
end
end
And in your view
<%= link_to 'x', remove_tag_project_path(project), :tag => tag, :method => :put %>
Of course you should add some sanitation, but it should work this way...
Based on #Vapire's suggested code - finally worked out a working solution. Just some minor edits to the view, route, and controller. Let me know if you see anything dodgy in here - still trying to get a good grasp of Ruby/Rails so all suggestions/refactoring ideas welcome.
The updated test site is at project-list.heroku.com.
Updated projects controller to find current project, remove :tag passed from index view through route:
def remove_tag
#project = Project.find(params[:id])
#project.type_list.remove(params[:tag])
#project.save
redirect_to projects_path, :flash => { :success => "Updated - tag nuked."}
end
Updated route:
resources :projects
match 'projects/:id/remove_tag/:tag' => 'projects#remove_tag'
Updated the link_to 'x' code to pass :tag params through the updated route above:
<% project.type_list.each do |tag|%>
<div class="p_tag">
<%= tag %>
<%= link_to 'x', {:action => "remove_tag", :id => project.id, :tag => tag,
:controller => "projects"} %>
</div>
<% end %>
This is obviously new ground for me so would appreciate if you have a different / better way of handling this issue please let me know! Also, thanks for your help #Vapire!

Resources