Allowing users to volunteer for a task via ajax - javascript

My app is setup where a user owns a task and other user's can volunteer to complete these tasks. My models are setup as so:
User
class User < ActiveRecord::Base
has_many :participations, foreign_key: :participant_id
has_many :owned_tasks, class_name: "Task", foreign_key: :owner_id
end
Participation (Join Table)
class Participation < ActiveRecord::Base
enum status: [:interested, :selected]
belongs_to :task
belongs_to :participant, class_name: "User"
end
Task
class Task < ActiveRecord::Base
enum status: [:open, :in_progress, :complete]
has_many :participations
has_many :participants, through: :participations, source: :participant
# Dynamically generates relations such as 'selected_participants'
Participation.statuses.keys.each do |status|
has_many "#{status}_participants".to_sym,
-> { where(participations: { status: status.to_sym }) },
through: :participations,
source: :participant
end
belongs_to :owner, class_name: "User"
end
What I would like do is simply allow users to click a button to volunteer for a task within that particular task's show view.
I can accomplish this with ease inside my rails console:
user = User.first
task = Task.first
user.owned_tasks << task
user_2 = User.find(2)
task.participants << user_2
Where I get stuck is trying to figure out how to setup the necessary controller code to get this to work. I'm also not sure how/where to create the conditional that checks if a user is already participating in a task is_participating?. Does it go in the join mode Participation or the Task table?
I think I have a vague idea on what the view should look like:
Task - Show View
<% unless current_user == #task.owner %>
<div class="volunteer-form">
<% if current_user.is_participating? %>
<%= render 'cancel' %>
<% else %>
<%= render 'volunteer' %>
<% end %>
</div>
<% end %>
_volunteer.html.erb:
<%= form_for(current_user.participations.build(participant_id: current_user), remote: true) do |f| %>
<div><%= f.hidden_field :participant_id %></div>
<%= f.submit "Volunteer" %>
<% end %>
_cancel.html.erb:
<%= form_for(current_user.participations.find_by(participant_id: current_user), html: { method: :delete }, remote: true) do |f| %>
<%= f.submit "Cancel" %>
<% end %>
JS
// create.js.erb
$(".volunteer-form").html("<%= escape_javascript(render('tasks/volunteer')) %>");
// destroy.js.erb
$(".volunteer-form").html("<%= escape_javascript(render('tasks/cancel')) %>");

From your view looks like is_participating? belongs in the User model, it should probably be something along these lines:
def is_participating?(task_id)
participations.where(task_id: task_id).exists?
end
As for the controller code you probably want something along these lines:
class ParticipationsController
# Note I assume you have access to current_user here
def create
participation = current_user.participations.create(participation_params)
respond_to do |format|
format.js
end
end
def destroy
participation = current_user.participations.find(params[:id])
participation.destroy
respond_to do |format|
format.js
end
end
protected
def participation_params
params.require(:participation).permit :task_id
end
end
On _volunteer.html.erb (Note that you now have to pass the task into the partial):
<%= form_for(current_user.participations.build, remote: true) do |f| %>
<%= f.hidden_field :task_id, value: task.id %>
<%= f.submit "Volunteer" %>
<% end %>

Related

Rails submitting a form through ajax

There is some problem with using ajax in rails. I want to create new post in my wellcome/inex page. But when I push the botton it show
No route matches [POST] "/welcome/index"
wellcome/index.html
<b>Projects</b>
<ul id="projects">
<% #projects.each do |project| %>
<h2><%= project.title %></h2>
<% end %>
</ul>
<br>
<%= form_for :project do |f| %>
<%= f.text_field :title, :placeholder => ' Enter new project here..' %>
<but><%= f.submit 'Add Project'%></but>
<% end %>
welcome_controller.rb
class WelcomeController < ApplicationController
def index
#projects = Project.all
#project = Project.new
end
def create
#project = Project.new(project_params)
respond_to do |format|
if #project.save
format.html { redirect_to #project, notice: 'project was successfully created.' }
format.js
format.json { render json: #project, status: :created, location: #project }
else
format.html { render action: "new" }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
private
def project_params
params.require(:project).permit(:title)
end
end
create.js.erb
$('#projects').html("<%= escape_javascript (render 'projects') %>");
routes project
Prefix Verb URI Pattern Controller#Action
project_index GET /project(.:format) project#index
POST /project(.:format) project#create
new_project GET /project/new(.:format) project#new
edit_project GET /project/:id/edit(.:format) project#edit
project GET /project/:id(.:format) project#show
PATCH /project/:id(.:format) project#update
PUT /project/:id(.:format) project#update
DELETE /project/:id(.:format) project#destroy
Spent a lot of time updating this with AJAX but failed everytime. Any there to help? Thanks in advance
You have to add remote: true into form.
<%= form_for #project, url: project_index_path, remote: true do |f| %>
<%= f.text_field :title, :placeholder => ' Enter new project here..' %>
<but><%= f.submit 'Add Project'%></but>
<% end %>
If you want integrate Ajax and Ruby you need to add the line below inside your link action
remote:true
also you can check this about Ajax and Ruby
How Ajax works with ruby on rails

How do I render a partial containing a form_for for an edit function?

I have a div in a parent view that renders a show partial. In this show partial, I have a button that should change the parent's partial to render the 'form' partial to edit the object, but I keep getting jammed up because it seems that the 'form' partial's form_for is missing the #project object.
How can I pass this object to the 'form' partial? Am I missing something else?
I am rather new to using AJAX. Happy to provide more information if you need.
The error I am getting in the server terminal is
ActionView::Template::Error (First argument in form cannot contain nil or be empty):
routes.rb
get 'switchProjectView', to: 'projects#switch_main_view'
resources :projects
projects_controller.rb
class ProjectsController < ApplicationController
def new
#project = Project.new
end
def create
#project = Project.new(project_params)
if #project.save
flash[:success] = "New Project Added"
redirect_to #project
else
flash[:danger] = "Project Not Added. Please Try Again"
end
end
def show
#project = Project.find(params[:id])
end
def index
#projects = Project.all
end
def update
#project = Project.find(params[:id])
if #project.update_attributes(project_params)
redirect_to #project
else
render 'edit'
end
end
def edit
end
def switch_main_view
respond_to do |format|
format.html
format.js
end
end
def project_params
params.require(:project).permit(:main_contact_name, :id, :main_contact_phone, :main_contact_email, :project_name)
end
end
show.html.erb
<div class="body">
<div class="center jumbotron heading-jumbo">
<h1><%= #project.project_name %></h1>
</div>
<div class="body-jumbo jumbotron" id='project-details'>
<%= render "projects/show" %>
</div>
</div>
switch_main_view.js.erb
$('#project-details').html("<%= j render :partial => 'projects/form' %>");
_form.html.erb
<%= form_for(#project) do |f| %>
<%= f.label :project_name %>
<%= f.text_field :project_name, class: 'form-control'%>
<%= f.label :main_contact_name %>
<%= f.text_field :main_contact_name, class: 'form-control'%>
<%= f.label :main_contact_phone %>
<%= f.text_field :main_contact_phone, class: 'form-control'%>
<%= f.label :main_contact_email %>
<%= f.text_field :main_contact_email, class: 'form-control'%>
<%= f.submit 'Save Project', class: 'btn btn-primary'%>
<% end %>
_show.html.erb
<div class='text-center center'>
<h3><strong>Project Information</strong></h2>
</div>
<h2>Start Date: Launch Date:</h1>
<span class='pull-right jumbo-btn-span'><%= link_to "Edit Project Information", switchProjectView_path(#project), remote: true, class:'btn btn-primary jumbo-btn' %></span>
<h2>Primary Conact's Name: <%= #project.main_contact_name %></h2>
<h2>Primary Conact's Phone: <%= #project.main_contact_phone %></h2>
<h2>Primary Conact's Email: <%= #project.main_contact_email %></h2>
Well, with each request, you need to re-initialize the variable in your controller method. In your case, you need to do the following:
def switch_main_view
#project = Project.new
respond_to do |format|
format.html
format.js
end
end
Doing so will enable you to get rid of the error: First argument in form cannot contain nil or be empty, but that won't enable you to do what you are actually trying to do.
When you use link_to, by default it goes for an HTML request as you can see in Rails logs, something like following will appear:
Processing by ProjectsController#switch_main_view as HTML
In order to get the JS response, you need to tell link_to that you are up for a JS response, not for an HTML response, and you can do by passing format: :js in link_to like:
<%= link_to "Edit Project Information", switchProjectView_path(#project, format: :js), remote: true, class:'btn btn-primary jumbo-btn' %></span>
remote: true will be there to make sure that you aren't reloading the contents of the page, rather you are trying to fetch something from the server in the background.
Your request "switchProjectView_path(#project)" only send object id to server, so you need to load the object to present it in view for response.
1. First the route should look like this so that, the url could contain object id:
get 'switchProjectView/:id', to: 'projects#switch_main_view', as:"switchProjectView"
2. You have to load the object in controller:
def switch_main_view
#project = Project.find(params[:id])
respond_to do |format|
format.html
format.js
end
end
Try passing the variable when calling the partial:
$('#project-details').html("<%= j render :partial => 'projects/form', locals: {project: #project} %>");

Act as Votable for nested Resources

I'm a newbie on rails and have created a listings and reviews app, (yelp type) and I want users to vote on the reviews, I have installed act as votable and configured as below but I seem to be missing something... any ideas why am getting that error?
Below is my routes.rb
Rails.application.routes.draw do
devise_for :users
resources :listings do
resources :reviews, except: [:show, :index, :upvote, :downvote] do
resources :user do
put "upvote", to: "reviews#upvote"
put "downvote", to: "reviews#downvote"
end
end
end
get 'pages/about'
get 'pages/how'
get 'pages/faqs'
get 'pages/contact'
get 'pages/privacy'
get 'pages/tos'
get 'pages/guidelines'
root 'listings#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Here is my show.html.erb, I will turn it into a partial when it works
<div class="row">
<div class="col-md-3">
<%= image_tag #listing.image_url (:medium) %>
<h3><%= #listing.name %></h3>
<div class="star-rating" data-score= <%= #avg_rating %> ></div>
<p class="small"><%= "#{#reviews.length} reviews" %></p>
<address>
<strong>Address:</strong>
<%= #listing.address %><br>
<strong>Phone:</strong>
<%= #listing.phone %><br>
<strong>Email:</strong>
<%= mail_to #listing.email %> <br>
<strong>Website:</strong>
<%= link_to #listing.website, #listing %>
</address>
<hr>
<p>
<strong>About:</strong>
<p><%= h(#listing.description).gsub(/\n/, '<br/>').html_safe %></p>
</p>
</div>
<div class="col-md-9">
<%= link_to "Haiya, Post a Review ", new_listing_review_path(#listing), class: "btn btn-info" %> <br><br>
<table class="table">
<thead>
<tr>
<th class="col-md-3"></th>
<th class="col-md-9"></th>
</tr>
</thead>
<tbody>
<% if #reviews.blank? %>
<tr>
<p>No reviews yet. Be the first to write one!</p>
<% else %>
<% #reviews.each do |review| %>
<td>
<h4> <%= "#{review.user.first_name.capitalize} #{review.user.last_name.capitalize[0]}." %></h4>
<p class = "small"><%= time_ago_in_words(review.created_at) %> ago </p>
</td>
<td>
<div class="star-rating" data-score= <%= review.rating %> ></div>
<p><%= h(review.comment).gsub(/\n/, '<br/>').html_safe %></p>
<%= link_to 'UP', listing_review_user_upvote_path(#listing, review), method: :put %>
<!-- Check if user is signed in, to enable edit and update -->
<% if user_signed_in? %>
<% if (review.user == current_user) || (current_user.admin?) %>
<%= link_to "Edit", edit_listing_review_path(#listing, review), class: "text-left" %>
<%= link_to "Delete", listing_review_path(#listing, review), method: :delete, class: "text-left" %>
<% end %>
<% end %>
</td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
</div>
<% if user_signed_in? && current_user.admin? %>
<%= link_to 'Edit', edit_listing_path(#listing), class: "btn btn-link" %> |
<% end %>
<%= link_to 'Back', listings_path, class: "btn btn-link" %>
<!--The script for star ratings. -->
<script>
$('.star-rating').raty({
path: 'https://s3.eu-west-2.amazonaws.com/bebuwaphotos/uploads/stars',
readOnly: true,
score: function() {
return $(this).attr('data-score');
}
});
</script>
Here is my reviews controller
class ReviewsController < ApplicationController
before_action :authenticate_user!
before_action :set_listing
before_action :set_review, only: [:show, :edit, :update, :destroy, :upvote, :downvote]
before_action :check_user, only: [:edit, :update, :destroy]
# GET /reviews/new
def new
#review = Review.new
end
# GET /reviews/1/edit
def edit
end
#Added the def upvote and downvote
def upvote
#review = Review.find(params[:id])
#review.upvote_from current_user
redirect_to :back
end
def downvote
#review = Review.find(params[:id])
#review.downvote_from current_user
redirect_to :back
end
# POST /reviews
# POST /reviews.json
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
#review.listing_id = #listing.id
respond_to do |format|
if #review.save
format.html { redirect_to #listing, notice: 'Your review was successfully posted.' }
format.json { render :show, status: :created, location: #review }
else
format.html { render :new }
format.json { render json: #review.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /reviews/1
# PATCH/PUT /reviews/1.json
def update
respond_to do |format|
if #review.update(review_params)
format.html { redirect_to root_url, notice: 'Your Review was successfully updated.' }
format.json { render :show, status: :ok, location: #review }
else
format.html { render :edit }
format.json { render json: #review.errors, status: :unprocessable_entity }
end
end
end
# DELETE /reviews/1
# DELETE /reviews/1.json
def destroy
#review.destroy
respond_to do |format|
format.html { redirect_to listing_path(#listing), notice: 'Your Review was successfully destroyed :(.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_review
#review = Review.find(params[:id])
end
#Check user
def check_user
unless (#review.user == current_user) || (current_user.admin?)
redirect_to root_url, alert: "Sorry, this review belongs to someone else, you can only edit reviews you have posted."
end
end
#set listing
def set_listing
#listing = Listing.find(params[:listing_id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def review_params
params.require(:review).permit(:rating, :comment)
end
end
And my model :
class Review < ApplicationRecord
belongs_to :user
belongs_to :listing
validates :rating, :comment, presence: true
validates :rating, numericality: {
only_integer: true,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 5,
message: "You have to select at least 1 star if not more stars.."
}
acts_as_votable
end
Finally here is my error
Please explain like you would to a newbie.. or a 5 year old .
Thank you Eshan, followed your answer and got the following error.
You are doing well as a newbie.
The first thing you do when you get an error related to routes, you run rails routes in commandline, so you can see your routes in front of you :
Problem:
You are having a problem with listing_review_user_upvote path, which leads to /listings/:listing_id/reviews/:review_id/user/:user_id/upvote(.:format) URI pattern as you see above.
This route needs three params: listing_id, review_id, and user_id, but you call it this way in your view:
listing_review_user_upvote_path(#listing, review).
You don't send user_id at all, meanwhile you don't even need it since you use current_user in your controller's action.
Solution:
I suggest you to update your routes to be:
resources :listings do
resources :reviews, except: [:show, :index] do
put "upvote", to: "reviews#upvote"
put "downvote", to: "reviews#downvote"
resources :user
end
end
And to use it in your view this way, with ids instead of objects :
listing_review_user_upvote_path(#listing.id, review.id)
These two steps will resolve your problem hopefully.
Finally, except option is used with resources to avoid generating some of its CRUD routes like: update, index, edit, you don't need to use it with your custom routes like: upvote and downvote.
Update:
You should use the correct param in your controller, review_id rather than id, change review = Review.find(params[:id]) to review = Review.find(params[:review_id])
I suggest you to use find_by(id: params[:review_id rather than find(params[:review_id because find method raises an exception if it couldn't find the object in db.

Dynamically adding prepopulated nested forms using cocoon and rails3-jquery-autocomplete

Thanks for your time.
I am working on rails 3.2 and I am using gems simple_form, cocoon, and rails3-jquery-autocomplete.
I have following models Machine, Part, PartUsage, MachineCosting and PartCosting
Machine and Part models with many-to-many association.
class Machine < ActiveRecord::Base
attr_accessible :name
has_many :part_usages
has_many :parts, :through => :part_usage
end
class Part < ActiveRecord::Base
attr_accessible :name
end
class PartUsage < ActiveRecord::Base
attr_accessible :machine_id,
:part_id
belongs_to :part
belongs_to :machine
end
MachineCosting and PartCosting models with one-to-many association.
class MachineCosting < ActiveRecord::Base
attr_accessible :machine_id,
:total_cost,
:machine_name,
:part_costings_attributes
attr_accessor :machine_name
has_many :part_costings, :dependent => :destroy
accepts_nested_attributes_for :part_costings, :allow_destroy => true
def machine_name
self.machine.try(:name)
end
end
class PartCosting < ActiveRecord::Base
attr_accessible :part_id,
:part_name,
:cost
attr_accessor :part_name
belongs_to :machine_costing
belongs_to :part
def part_name
self.part.try(:name)
end
end
Form for MachineCosting
views/machine_costings/_form.html.erb
<%= simple_form_for(#machine_costing, :html => {:multipart => true}) do |f| %>
<%= f.input :machine_id, :url => autocomplete_machine_id_machines_path,
:as => :autocomplete %>
<!-- HERE, WHENEVER I SELECT A MACHINE, I WANT TO ADD NESTED-FORMS FOR
PARTCOSTINGS CORRESPONDING TO ALL THE PARTS OF THE MACHINE I JUST SELECTED.-->
<%= f.simple_fields_for :part_costings do |part_costing|%>
<%= render 'part_costings/part_costing_fields', :f => part_costing %>
<% end %>
<% end %>
Please suggest how can I populate and dynamically add fixed no of fields for PartCostings using javascript after machine_id is selected in the field.
I would be happy to provide any more information.
Thanks again!!
First, summary of my approach.
In MachineCosting form, use 'rails3-jquery-autopopulate' gem to search for Machines by their name.
Hack MachineController to also send 'Parts info' together with matching machines in json form.
When user selects a machine, use javascript to add nested_forms for PartCostings and partially populate them using data recieved in json form.
Now here is how I have done it (code part).
views/machine_costings/_form.html.erb
<%= simple_form_for(#machine_costing, :html => {:multipart => true}) do |f| %>
<%= f.error_notification %>
<%= f.input :machine_name, :url => autocomplete_machine_name_machines_path,
:as => :autocomplete, :input_html => { :id_element => "#machine_costing_machine_id"} %>
<%= f.association :machine, :as => :hidden %>
<div id='part_costings'>
<%= f.simple_fields_for(:part_costings) do |part_costing|%>
<%= render 'part_costings/part_costing_fields', :f => part_costing %>
<% end %>
<div class="links hidden">
<%= link_to_add_association 'Add part costings', f, :part_costings,
:partial => 'part_costings/part_costing_fields' %>
</div>
</div>
<%= f.button :submit%>
<% end %>
views/part_costings/_part_costing_fields.html.erb
<div class= 'nested-fields'>
<!-- DO NOT CHANGE THE ORDER OF ATTRIBUTES ELSE machine_costing.js WILL FAIL -->
<%= f.input :part_id, :as=> :hidden %>
<%= f.text_field :part_name, :disabled => 'disabled' %>
<%= f.input :cost %>
<%= link_to_remove_association '<i class="icon-remove"></i>'.html_safe, f ,:class =>"part_costing_remove" %>
</div>
controllers/machines_controller.rb
class MachinesController < ApplicationController
# OVER RIDING THIS BY OWN METHOD
# autocomplete :machine, :name, :full=> true, :extra_data => [:machine_id]
def autocomplete_machine_name
respond_to do |format|
format.json {
#machines = Machine.where("name ilike ?", "%#{params[:term]}%")
render json: json_for_autocomplete_machines(#machines)
}
end
end
def json_for_autocomplete_machines(machines)
machines.collect do |machine|
parts = Hash.new
unless machine.part_usages.empty?
machine.part_usages.each do |part_usage|
parts[part_usage.part.id] = part_usage.part.name
end
end
hash = {"id" => machine.id.to_s, "value" => machine.name,
"parts" => parts}
hash
end
end
#
#
#
end
And here is the javascript part.
assest/javascripts/machine_costings.js
$(function() {
// Part Costings
var part_id;
var part_name;
$('#machine_costing_machine_name').bind('railsAutocomplete.select', function(event, data){
console.debug("Machine selected...");
parts = data.item.parts;
console.debug("Removing existing part_costings...");
$('.part_costing_remove').click();
console.debug("Adding part_costings...");
for(var id in parts) {
part_id = id;
part_name = parts[id];
$('.add_fields').click();
}
console.debug("Done adding all part_costings...");
});
$('#part_costings').bind('cocoon:after-insert', function(e, part_costing) {
part_costing[0].getElementsByTagName('input')[0].value = part_id;
part_costing[0].getElementsByTagName('input')[1].value = part_name;
console.debug("Added part_costing...");
});
});
routes.rb
resources :machines do
get :autocomplete_machine_name, :on => :collection
end
The critical points in javascript solution are...
Method "bind('railsAutocomplete.select', function(event, data)" from rail3-jquery-autocomplete gem.
Method "bind('cocoon:after-insert', function(e, part_costing)" from cocoon gem.
That's all.
Please let me know if you have some suggestions. Thanks :)

Ajax/Rails Nested Comments

I have an application where book_comments belongs_to books.
My routes file :
resources :books do
resources :book_comments
end
I have a book_comment form on show.html.erb for books.
def show
#book = Book.find(params[:id])
#new_book_comment = #book.book_comments.build
end
I'm using ajax to post the book_comments to the book show page via a partial: _book_comment_form.html.erb :
<%= form_for([#book, #new_book_comment], remote: :true) do |f| %>
<div class="field" style="margin-top:60px;">
<%= f.text_area :comment, rows: 3 %>
<%= f.submit "Add Comment" %>
</div>
<% end %>
When #new_book_comment is called it refers to a book_comments_controller create action:
def create
#book = Book.find(params[:book_id])
#book_comment = current_user.book_comments.build(params[:book_comment]) do |f|
f.book_id = #book.id
end
if #book_comment.save
respond_to do |format|
format.html { redirect_to #book }
format.js { render :layout => false }
end
end
end
The book_comment is saved but my problem comes in when trying to render via ajax in the _book_comments partial:
<div id="comments">
<% #book_comments.each do |book_comment| %>
<%= render 'comment', :book_comment => comment %>
</div>
<% end %>
</div>
via: create.js.erb in the book_comments folder
$("div#comments").append("<%= escape_javascript(render('books/comment'))%>")
To sum, I'm having trouble making the create.js.erb in book_comments render to the book show page. Why wouldn't create.js.erb in the book_comments folder know to look at the objects in the and infer the new book comment should go inside the div?
My error message:
undefined method `comment' for nil:NilClass
don't understnd why. Seeing that I'm passing the instance variable to the comment partial.
Your partial's doing it wrong. You're passing a not referenced value (comment) to a not used variable (book_comment).
Instead of:
<% #book_comments.each do |book_comment| %>
<%= render 'comment', :book_comment => comment %> ....
you should do:
<% #book_comments.each do |book_comment| %>
<%= render 'comment', :comment => book_comment %>

Categories

Resources