I have OrdersController show action (with order_info partial), which displays current status of the order ("paid", "canceled", etc.).
meanwhile, I have callback action order_callback, which executes update action and changes status of the order in the database when it receives the callback from a payment processor.
What I want to achieve is to update show action in real-time to capture changes in order status (e.g. order paid successfully).
I tried to use unobtrusive javascript, but did not succeed.
update.js.erb
$("#order").html("<%= escape_javascript(render 'order_info') %>")
show.html.erb
<div id="order">
<%= render 'order_info' %>
</div>
orders_controller.rb
def update
if #order.update_attributes(order_params)
flash[:success] = "Order updated."
redirect_to #order
else
render 'edit'
end
end
api/orders_controller.rb
def order_callback
signature = request.headers['X-Signature']
request_uri = URI(env['REQUEST_URI']).request_uri rescue env['REQUEST_URI']
if $processor.callback_valid?(signature, request_uri)
#order = Order.find(params["id"])
#order.update_attributes(status: params["status"])
render status: :ok,
json: { success: true,
info: "Successfully updated order." }
else
render status: :unprocessable_entity,
json: { success: false }
end
end
I am using rails 4.2.2 with turbolinks enabled.
I was able to resolve it with javascript polling. The critical line was to explicitly say which .js partial to render in respond_to block
show.js.erb
$("#order").html("<%= j render 'orders/order_info', locals: {order: #order} %>");
OrderPoller.poll();
orders_controller.rb
def show
#order = Order.find(params[:id])
respond_to do |format|
format.js { render "orders/show.js.erb" }
format.html
end
end
orders.coffee
#OrderPoller =
poll: ->
setInterval #request, 5000
request: ->
$.get($('#order').data('url'))
jQuery ->
if $('#order').length > 0
OrderPoller.poll()
show.html.erb
<%= content_tag :div, id: "order", data: { url: order_path(#order) } do %>
<%= render 'order_info' %>
<% end %>
Related
I'm working on an dynamic edit of an article using rails. On each articles can be added paragraphs. I wanted to use Ajax with Jquery following the 136-jquery-ajax-revised tutorial from railscast. I created the template for the form and the new.js.erb response but I keep geting same error:
ParagraphsController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: []
This is the link that request the new form from view/article/show
<%= link_to 'New Paragraph', new_article_paragraph_path(#article), remote: true, class: 'uk-button uk-button-primary' %>
view/paragraph/_form
<div class="article-form-container">
<%= form_with scope: :paragraph, url: article_paragraphs, remote: true do |f| %>
<%= f.text_field :title, class: 'uk-input', placeholder: 'Title of paragraph (optional, can be a subtitle of the article)' %>
<%= f.text_area :text, class: 'uk-textarea', placeholder: 'Content of paragraph' %>
<%= f.hidden_field :position, :value => 3 %>
<div class="submit-button">
<%= f.submit class: 'uk-button uk-button-primary' %>
</div>
<% end %>
</div>
routes
resources :articles, only: %i[show update destroy create]
resources :articles do
resources :paragraphs, only: %i[new create update destroy]
end
view/paragraphs/new.js.erb
$('div#article-control-panel').hide("fast", function () {
$('div#article-control-panel').after('<%= j render 'form' %>')
})
controllers/paragraphs_controller
class ParagraphsController < ApplicationController
def new
#paragraph = Paragraph.new
end
def create
#paragraph = Paragraph.new(paragraph_params)
#article = Article.find(params[:article_id])
if #article.user == current_user
#paragraph.article = #article
#paragraph.save
end
respond_to do |format|
format.html { redirect_to #article }
format.js
end
end
def paragraph_params
params.require(:paragraph).permit(:title, :text, :position)
end
end
Can't figure out what the problem is. The error happens in the article page, after I press the link button.
Edit
Strange thing is that if i try to change the url of orm in something like article_path it will work...
Your controller is missing a respond_to. Your new function should look like this:
def new
#paragraph = Paragraph.new
respond_to { |format| format.js }
end
This respond_to allows rails to call the relevant .js.erb file, in this instance your new.js.erb file.
Just keep in mind that when you want to perform an ajax call, you require these few elements:
A respond_to { |format| format.js } in your controller action
Your js.erb file needs to be the same name as your controller action (foo.js.erb)
A partial to render through your js.erb file, typically named the same as your js file. (_foo.js.erb)
If it is a link, you need remote: true. If it is a form and you're using rails 5, you could use form_with or simply include remote: true too.
my route.rb
post 'home/create'
get 'home/create'
my HomeController
def create
#review_n = Review.create(review_params)
if #review_n.errors.empty?
respond_to do |format|
format.js { render 'create', locals: {review_name: #review_n.review_n, review_body: #review_n.review_body} }
end
else
render 'index'
end
end
my create.js.erb
$(function() {
$(".wrap-body").append("<div> tmp </div>");
});
rails say: ActionController::UnknownFormat in HomeController#create
I want send data in my html.erb without reload page. Help me, please!
UPD:
my html.rb
<%= form_tag home_create_path, :method => 'post', :remote => true do %>
<%= text_area_tag 'review[review_body]', nil %>
<%= text_field_tag 'review[review_name]', nil %>
<%= submit_tag 'send' %>
<% end %>
Is your code hitting the render 'index' call that is outside of the respond_to block?
Normally you would put all render calls inside the respond_to block, so it's obvious that all paths of your logic can respond to all expected formats:
def create
#review_n = Review.create(review_params)
respond_to do |format|
if #review_n.errors.empty?
format.js { render 'create', locals: {review_name: #review_n.review_n, review_body: #review_n.review_body} }
else
format.js { render 'index' }
end
end
end
This requires having both a create.js.erb and an index.js.erb (for the error case).
Also, as #sahil recommended, your routes.rb should not declare get 'home/create' - this action modifies data, so it isn't safe to make it accessible via GET.
I want to create a wizard in js.
steps :first_step,
:second_step
In my 'controller_step'
def show
case step
when :first_step
#r = R.new
when :second_step
end
render_wizard
end
def update
case step
when :first_step
#r = R.new(r_params)
when :second_step
end
render_wizard #r
end
I have problems after the update of the first step. I'm receive the following error message:
"Missing template controller_step/second_step, application/second_step
with {:locale=>[:en], :formats=>[:html], :variants=>[],
:handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}. ".
How can I force loading of js templete? I would load "second_step.js.erb".
I tried to change the update method:
respond_to do |format|
format.js { render :js => ( render_wizard #r ) }
end
Of course I get the following error:
"AbstractController::DoubleRenderError in ...Controller#update Render
and/or redirect were called multiple times in this action. Please note
that you may only call render OR redirect, and at most once per
action. Also note that neither redirect nor render terminate execution
of the action, so if you want to exit an action after redirecting, you
need to do something like "redirect_to(...) and return"."
I also tried to change the code (in update):
respond_to do |format|
format.js { render :js => ( render_wizard #room_types and return ) }
end
I'm get the same error ( ... application/second_step with {:locale=>[:en], :formats=>[:html] .... )
P.S.
In view of the first step:
<%= form_for(#r, url: wizard_path, method: :put, remote: true) do |f| %>
....
<%= f.submit "Submit", class: "btn btn-default" %>
<% end %>
How do I fix ? thanks in advance
The #render_wizard method defined in the Wicked::Controller::Concerns::RenderRedirect is a wrapper method around the ActionController::Base#render method. It
accepts a hash of options and passes it down to the controller's regular #render method.
This is the source code from the Wicked library:
def render_wizard(resource = nil, options = {})
...
if #skip_to
...
else
render_step wizard_value(step), options
end
end
def render_step(the_step, options = {})
if the_step.nil? || the_step.to_s == Wicked::FINISH_STEP
...
else
render the_step, options #<-- Here
end
end
Your code:
respond_to do |format|
format.js { render :js => ( render_wizard #r ) }
end
is basically doing:
respond_to do |format|
format.js { render :js => ( render #r ) }
end
which is in fact calling the render method twice.
As it is searching for a .html template rather than a .js.erb one, try adding a formats: 'js' option to the render_wizard method. It should prepend ['js'] to the :formats=>[:html] we see in the Missing template error message.
respond_to do |format|
format.js { render_wizard(#r, formats: 'js') }
end
also, make sure the template's filename follows the rails convention and start with a _. (ie: _second_step.js.erb)
About the double render error, you are correct. You must return from the controller #show or #update method and prevent further code from calling the #render method a second time. You seem to have fixed that problem already.
EDIT#1
It seems like you may be able to call this method directly in your controller.. :
def render_step(the_step, options = {})
# Wicked::FINISH_STEP = "wicked_finish"
if the_step.nil? || the_step.to_s == Wicked::FINISH_STEP
redirect_to_finish_wizard options
else
render the_step, options
end
end
I believe the_step will be the partial's name. I think you should be able to call the #render_step method from your controller.
You may be able to do:
def show
respond_to do |f|
f.js do
case step
when :first_step
#r = R.new
render_step(step) and return
when :second_step
...
end
end
end
end
I solved in this way:
in r model:
validates :x, presence: true
in step controller :
...
steps :first_step,
:second_step,
:finish_step
...
def show
case step
when :first_step
#room_types = R.new
end
render_wizard
end
def update
case step
when :view_new_room_type_step
#r = R.create(r_params)
when :view_desc_step
#r = R.find(params[:r][:id])
#r.update(r_params)
end
respond_to do |format|
if !#r.errors.any?
format.js { !next_step?(:finish_step) ? ( render next_wizard_path ) : ( redirect_to_finish_wizard ) }
else
format.js { render wizard_path, r: #r }
end
end
end
private
...
def redirect_to_finish_wizard
redirect_to r_index_path , notice: "Ok"
end
in first_step.js.erb
$("#modal").html("<%= escape_javascript(render 'form_first_step') %>")
in _form_first_step.html.erb:
<% if #r.errors.any? %>
<div class="alert fade in alert-danger alert-dismissable"><button aria-hidden="true" class="close" data-dismiss="alert" type="button">×</button>
<ul>
<% #r.errors.full_messages.each do |msg| %>
<%= content_tag :li, msg, :id => "error_#{msg}" if msg.is_a?(String) %>
<% end %>
</ul>
</div>
<% end %>
<%= form_for(#room_types, url: wizard_path, method: :put, remote: true) do |f| %>
...
<% end %>
in second form ( of second step ):
<%= form_for(#room_types, url: wizard_path(:second_step), method: :put, remote: true) do |f| %>
....
<%= f.hidden_field :id %>
<% end %>
Idea: to validate the data in the steps you can use jquery-form-validator-rails
(I have been coding for barely a month so apologies if it's a stupid question). I have a user model just with name, email and type. I have created an index form that you can filter by type and it should show the results.
Form and filters show as expected by I have 2 problems:
1. The usertype is duplicated. For example, if I have 5 users (created with the faker gem) each one of them is customer or supplier, the filter shows customer and supplier 5 times instead of twice
2. When I select a filter, it all results are shown and they are not filtered.
This is my model:
class User < ActiveRecord::Base
has_many :microposts
scope :by_userType, -> userType { where(:userType => userType) }
This is my Controller
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
has_scope :by_userType, type: :array #, :using => [:userType]
# GET /users
# GET /users.json
#def index
# #users = User.all
#end
def index
#users = apply_scopes(User).all
end
# GET /users/1
# GET /users/1.json
def show
end
# GET /users/new
def new
#user = User.new
end
# GET /users/1/edit
def edit
end
# POST /users
# POST /users.json
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user }
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /users/1
# PATCH/PUT /users/1.json
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# DELETE /users/1
# DELETE /users/1.json
def destroy
#user.destroy
respond_to do |format|
format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:name, :email, :userType)
end
end
This is my form:
<p id="notice"><%= notice %></p>
<h1>Listing Users</h1>
<%= form_tag users_path, method: 'get', id: "users_search" do%>
<% #users = User.all %>
<% #users.each do |user|%>
<%= check_box_tag "by_userType[]", user.userType %><%= user.userType %><br>
<% end %>
<%= submit_tag "submit" %>
<% end %>
<div id="users"><%= render 'user_list' %></div>
<script type="text/javascript">
$(function () {
$('input[type=checkbox]').change(function(){
$.get($('#users_search').attr('action'),
$('#users_search').serialize(), null, 'script');
return false;
});
$('users_search').submit(function() {
$.get(this.action, $(this).serialize(), null, 'script');
return false;
});
});
</script>
Thank you in advance for your time!
So a couple of things here. First of all, welcome to rails, and welcome to Stack Overflow.
+1 for asking a question and providing code examples for what you've done so far.
Please note that for standardization, case is important in rails. For declaration of scopes you should use snake case. UserModel is the class name, user_model would be snake case.
Now as far as the actual implementation, I would do it somewhat differently. If you notice most of the ajax-filtering of one model based on a field doesn't use the same model as the parameter, it's not hte ONLY way, but I prefer it, as it allows flexibility for adding extra fields to user_type later. Meaning, if you extract user type into it's own model, then you can easily filter your users from the user_type attribute of :name.
So your user model would have:
class User < ActiveRecord::Base
has_many :microposts
belongs_to :user_type
scope :by_user_type, -> user_type { where(:user_type => user_type) }
end
Then create a new model called user_model
rails g scaffold user_model name:string slug:string avatar:string
** Note that the additional fields are just examples, name is hte only necessary one. But if you want to let users do url searches, the slug is easy to use. i.e. yoursite.com/user_type/sellers
Now create a migration to remove your existing userType field in users and a new one for the relationship.
rails g migration modify_user_type_in_user
And the contents of that file would be
class ModifyUserTypeInUser < ActiveRecord::Migration
def change
remove_column :users, :userType
add_reference :users, :user_type, index: true
end
end
Now edit the new user_type.rb model and add the relationship for users
class UserType < ActiveRecord::Base
has_many :users
end
You also need to use UJS, which you didn't mention in your post. When your form field is clicked on, it's sending a javascript (ajax) request. This means that in order to change the data, you'll need a javascript response template.
So add the file app/views/users/index.js.erb and put inside it these contents:
$("#users").html('<%= j(render("user_list", users: #users)) %>');
$("table tbody tr:nth-of-type(even)").css("background","#BD9FB1");
Lastly, you'll need to change your form, so it represents the correct searchable model. So edit 'app/views/users/index.html.erb'
<p id="notice"><%= notice %></p>
<h1>User Filter</h1>
<%= form_tag users_path, method: 'get', id: "users_search" do%>
<% #user_types = UserType.all %>
<% #user_types.each do |user_type|%>
<%= check_box_tag "by_user_type[]", user_type.id %><%= user_type.name %><br>
<% end %>
<%= submit_tag "submit" %>
<% end %>
<div id="users"><%= render 'user_list' %></div>
<script type="text/javascript">
$(function () {
$('input[type=checkbox]').change(function(){
$.get($('#users_search').attr('action'),
$('#users_search').serialize(), null, 'script');
return false;
});
$('users_search').submit(function(e) {
e.preventDefault();
$.get(this.action, $(this).serialize(), null, 'script');
return false;
});
});
</script>
i'd like to make a like/dislike ability on my RoR application. How can i make it via Ajax-requests ?
dislike and like - are integer how can i make an Ajax-request, than i can send the data of what i want to increment either "like" or "dislike" counter in my methods
I have a table with posts :
#app/views/dashboard/view.html.erb
<table>
<%if #post.count!=0%>
<%#post.each do |p|%>
<%if !p.text.nil?%>
<tr>
<td><b class="margin"><h4><%=p.text%></b></h4></td>
<td>by <%=p.user.username%> </td>
<td><span class="glyphicon glyphicon-thumbs-up likeAction"><%= link_to p.like, dashboard_like_path, :remote => true, :id => 'likecount' %> </td>
<td><span class="glyphicon glyphicon-thumbs-down"><%= link_to p.dislike, dashboard_dislike_path, :remote => true, :id => 'dislikecount' %> </td>
<%end%>
<% end %>
<%else%>
There's no posts yet, but you can add <%=link_to "one", dashboard_posts_create_a_post_path%>
<%end%>
</table>
My js file
#app/views/dashboard/view.js
$('#likecount').text(#post.like);
$('#dislikecount').text(#post.dislike);
my methods in controller :
#app/controller/dahsboard_controller.rb
def like
#post.increment!(:like)
respond_to do |format|
format.html
format.js
end
end
def dislike
#post.increment!(:dislike)
respond_to do |format|
format.html
format.js
end
end
My dashboard.js in assets/javascripts
jQuery(function($) {
$("likeAction").click(function(){
$.ajax({
url: dashboard_like_path,
type: 'POST',
success: function(){
$('#linkcount').text(data);
}
error: function(error){
alert(error);
}
});
});
});
You already have Rails built-in AJAX functionality, so no need for calling $.ajax. Simply set remote: true on your link_to 'Like', ..., remote: true and respond with the same code you have in app/views/dashboard/view.js: format.js { render action: 'view' }
EDIT: As long as like and dislike are set as member routes on posts:
dislike_post POST /posts/:id/dislike(.:format) posts#dislike
like_post POST /posts/:id/like(.:format) posts#like
You will have a params[:id] (if you send one) to do something like #post = Post.find(params[:id]), if you share this code with show, like and dislike. You can create a set_post before filter, so you don't repeat yourself.
You'll probably want to look at a gem called acts_as_votable
This sets much of your model functionality up - allowing you to use the likes of #post.downvote_from #user2 etc. I'll let you look into that, as it's what you need in the backend I think.
In regards the front-end (especially Ajax), you'll have to set up a controller action, and then hit it with a JS request:
#config/routes.rb
resources :posts do
match :vote, via: [:post,:delete]
end
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
respond_to :js, :html, only: :vote
def vote
if request.delete?
#downvote
elsif request.post?
#upvote
end
end
end
This will allow you to use the following:
#app/views/posts/vote.js.erb
$(".element").html("<%=j render partial: "post/vote_count", object: #post %>");
#app/views/posts/index.html.erb
<% #posts.each do |post| %>
<%= render partial: "post/vote_count", object: :post %>
<% end %>
#app/views/posts/_vote_count.html.erb
<% method = #post.liked_by(current_user)
<%= link_to post.likes, post_vote_path(post), method: :post, remote: true %>
--
The Ajax functionality is pre-built into Rails; you have to be wary of which controller action it's going to send you to, as well as the response given.
My above code uses the respond_to block to invoke the .js.erb response -- allowing you to perform some actions when you send your request.