context testing rails-ujs functionality with Rails 7 turbo streams as a full replacement.
Class nesting is structured as follows:
class Cartitem < ApplicationRecord
belongs_to :article
belongs_to :cart
validates :quantity_ordered, numericality: { greater_than: 0 }, on: :create
validates :quantity_ordered, numericality: { greater_than_or_equal_to: 0 }, on: :update
end
A page of articles can potentially create cartitems, one per article.
Each article needs to be creatable, updatable, deletable (so it may be recreated for users who change their minds incessantly).
Accordingly, that page has a block for each article
<% articles.each do |article| %>
<%= render 'article', article: article %>
<% end %>
# article partial
<div>
<%= article.description_short %>
</div>
<div>
<%= turbo_frame_tag dom_id(article) do %>
<%= render 'cartitem_block', article: article %>
<% end %>
</div>
the partial then invokes a turbo frame:
<%= turbo_frame_tag dom_id(article) do %>
<%= form_with(model: #cartitem, local: false) do |f| %>
<%= f.number_field :quantity_ordered %>
<%= f.submit %>
<% end %>
<% end %>
The rendered HTML does call the dom_ids and renders as expected:
<turbo-frame id="article_1">
<form action="/cartitems" accept-charset="UTF-8" data-remote="true" method="post"><input type="hidden" name="authenticity_token" value="sgABxVmZX0TDditdtwuGrgG4t9fkdfFMg02tpDnfgX3ch_IqD_YGoFJroE4u3y9t-bdLfGAyXZBUyJe04RBqtQ" autocomplete="off">
<input min="1" type="number" name="cartitem[quantity_ordered]" id="cartitem_quantity_ordered">
<button name="button" type="submit" ></button>
</form>
</turbo-frame>
[...]
<turbo-frame id="article_5">
<form action="/cartitems" accept-charset="UTF-8" data-remote="true" method="post"><input type="hidden" name="authenticity_token" value="..." autocomplete="off">
<input min="1" type="number" name="cartitem[quantity_ordered]" id="cartitem_quantity_ordered">
<button name="button" type="submit" ></button>
</form>
</turbo-frame>
The controller:
def create
#article = Article.find(params[:cartitem][:article_id])
price_ordered = #article.sell_price * params[:cartitem][:quantity_ordered].to_d
#cartitem = Cartitem.create!(article_id: #article.id, quantity_ordered: params[:cartitem][:quantity_ordered].to_d, price_ordered: price_ordered, cart_id: params[:cartitem][:cart_id].to_i)
respond_to do |format|
if #cartitem.save
format.turbo_stream
format.html { redirect_to cartitem_url(#cartitem), notice: "Cartitem was successfully created." }
else
format.turbo_stream
format.html { render :new, status: :unprocessable_entity }
end
end
end
If #cartitem is valid, the process runs and renders as expected:
Rendered cartitems/_cartitem.html.erb (Duration: 1.0ms | Allocations: 953)
Rendered cartitems/create.turbo_stream.erb (Duration: 1.3ms | Allocations: 1082)
However upon submitting with an empty value, to test the validation, an unexpected result occurs:
Started POST "/cartitems" for 127.0.0.1 at 2022-01-22 08:24:38 +0100
Processing by CartitemsController#create as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "cartitem"=>{"price_ordered"=>"3.41", "cart_id"=>"1", "article_id"=>"1", "fulfilled"=>"false", "quantity_ordered"=>""}}
[...]
TRANSACTION (0.2ms) ROLLBACK
TRANSACTION (0.2ms) ROLLBACK
↳ app/controllers/cartitems_controller.rb:28:in `create'
Completed 422 Unprocessable Entity in 11ms (ActiveRecord: 1.1ms | Allocations: 4874)
ActiveRecord::RecordInvalid (Validation failed: Quantity ordered must be greater than 0):
As expected, the transaction rolls back. However, the response is an Unprocessable Entity.
and the return is not what the create.turbo_stream.erb is stated to do, i.e. if the cartitem is nil re-render the partial.
*note: the else portion of the respond_to block was tried both with and without a reference to turbo_stream with identical result.
<% if #cartitem %>
<%= turbo_stream.replace "article_#{article.id}", #cartitem %>
<% else %>
<%= render 'cartitem_block', article: #article %>
<% end %>
# _cartitem partial:
<%= turbo_frame_tag dom_id(#cartitem.article), class: 'fade-text' do %>
<%= form_with(url: update_q_cartitem_path(#cartitem), local: false, method: :patch) do |form| %>
<%= form.number_field :quantity_ordered, value: number_with_precision(#cartitem.quantity_ordered, precision: 0), min: 0, style: 'width: 69px; display: inline-block;' %>
<%= button_tag(style: 'display: inline-block;') do %>
<p class='button warning' style='margin-top: 10px;'><%= fa_icon('recycle', class: 'fa 3x') %></p>
<% end %>
<% end %>
<% end %>
Instead, the xhr response returned is the HTML page of an error returned when in development mode.
The browser console in addition complains with
Response has no matching <turbo-frame id="article_1"> element
which is untrue as the HTML still has a lingering:
<turbo-frame id="article_1"></turbo-frame>
What needs to be done to properly error handle the case, render the form partial in its rightfully recognized place?
The condition where params submitted do not create a valid object should be explicit, particularly as the situation depends on a parent object:
else
format.turbo_stream { render turbo_stream: turbo_stream.replace( #article, partial: 'carts/cartitem_block', locals: { article: #article } ) }
end
otherwise the turbo_stream insists on trying to render the create turbo_stream variant which, in one case is calling a NULL object #cartitem and thus fails and is not able to proceed.
In your controller, you save the cartitem twice. The first instance (create!) throws an exception and stops the processing. Instead of
#cartitem = Cartitem.create!(blaa blaa)
Try
#cartitem = Cartitem.new(blaa blaa)
Now the object will get validated at the #cartitem.save step, and your turbo_stream file should load.
Also, and not related to the error, you could use strong parameters to make the controller action tidier and improve security.
Related
I am trying to save data, but on submitting the data, it just refreshes the page, but not actually stores data in table. and even it is not giving any errors too. What I want to do is after saving data, control should come on same page with empty textboxes.
Using Ruby 2.4.1, Rails 5.1.3, sqLite3
This is content of feedback_controller.rb
class FeedbackController < ApplicationController
def new
#feedback = Feedback.new
end
def create
#feedback = Feedback.new(feedback_params)
redirect_to root_path
respond_to do |f|
if #feedback.save
f.html {redirect_to #feedback, notice: 'feedback submitted'}
f.json {render :new, status: :created, location: :#feedback}
else
f.html {render :new }
f.json {render json: #feedback.errors, status: :unprocessable_entity}
end
end
end
private
# def set_feedback
# #feedback = Feedback.find(params[:id])
# end
def feedback_params
params.require(:feedback).permit(:name, :email, :message)
end
end
And here is new.html.erb file.
<%= form_with scope: :feedback, url: feedback_new_path, local: true do |form| %>
<% if #feedback.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(feedback.errors.count, "error") %> prohibited this feedback from being saved:</h2>
<ul>
<% #feedback.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name, id: :feedback_name %>
</div>
<div class="field">
<%= form.label :email %>
<%= form.text_field :email, id: :feedback_email %>
</div>
<div class="field">
<%= form.label :message %>
<%= form.text_area :message, id: :feedback_message %>
</div>
<div class="actions">
<%= form.submit "Submit Feedback"%>
</div>
<% end %>
<%= link_to 'Home', root_path %>
This is what puma server saying on log while storing data:
Started POST "/feedback/new" for 127.0.0.1 at 2017-09-14 21:33:41 +0530
Processing by FeedbackController#new as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Dk1sGiKYryIOeU6HyO7zeX6wqXazj9BUfooJgIDH1JjHxuKwA5MT0E6zvuwFYXGA8xEOz+tGAiXWySKv7voKvg==", "feedback"=>{"name"=>"sheshang", "email"=>"sid#gm.co", "message"=>"this is demo message"}, "commit"=>"Submit Feedback"}
Rendering feedback/new.html.erb within layouts/application
Rendered feedback/new.html.erb within layouts/application (3.8ms)
Completed 200 OK in 75ms (Views: 55.2ms | ActiveRecord: 1.3ms)
In addition to this problem, I want to know that how to debug in rails app. Like in C or any other program we used to put printf statements to check whether the control is going inside or not. How we can do in Rails?
As you can see, you're posting to the new action:
Started POST "/feedback/new" for 127.0.0.1 at 2017-09-14 21:33:41 +0530
Processing by FeedbackController#new as HTML
But the new action doesn't do a save.
So, you probably want:
<%= form_with scope: :feedback, url: feedbacks_path, local: true do |form| %>
By convention, if you do (in your routes.rb file):
resources :feedbacks
then you should get:
feedbacks GET /feedbacks(.:format) feedbacks#index
POST /feedbacks(.:format) feedbacks#create
new_feedback GET /feedbacks/new(.:format) feedbacks#new
edit_feedback GET /feedbacks/:id/edit(.:format) feedbacks#edit
feedback GET /feedbacks/:id(.:format) feedbacks#show
PATCH /feedbacks/:id(.:format) feedbacks#update
PUT /feedbacks/:id(.:format) feedbacks#update
DELETE /feedbacks/:id(.:format) feedbacks#destroy
As you can see, this does not generate a POST verb for the new action, which suggests that you're not using conventional routes. If that is true, then you may need to update your routes so that you get feedbacks_path.
I recommend following convention as it often makes life easier.
As far as printing to console, I tend to do:
puts "#{self.class}.#{__method__}, var: #{var}"
I replace var with whatever variable I want to inspect. I like to include the #{self.class}.#{__method__} bit because it let's me know which class and method I'm in.
I'd like to pass parameter not related to model by form_for on rails.I want to pass 'address' data, that is not related to product model, from view to controller. The following is my code, however it returned the next error. Could you tell me how to solve this problem?
Completed 500 Internal Server Error in 11ms (ActiveRecord: 0.1ms)
ActionView::Template::Error (wrong number of arguments (1 for 2..5)):
products_controller
class ProductsController < ApplicationController
~~~~
def transfer
#product = Product.new
end
def transfer_done
my_address = params[:my_address]
wallet = Wallet.find_by(address:my_address)
redirect_to root_url
end
~~~~
end
transfer.html.erb
<% provide("transfer") %>
<p>
<%= form_for #product, :url => {:action => 'transfer_done'} do |f| %>
<div>
<%= check_box :my_address %> <---Error occurred here.
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
</p>
You may try <%= text_field_tag :my_address %> inside your form
I have a Opportunity Model that has many Links. Links is a nested resource of Opportunity. On my views/opportunities/show.html.erb page, I display all the links that belong to that opportunity and I rendered a "new link" form as well. This form worked fine until recently and I am not sure why. When I fill out the "new link" form and click "add", the record does not save. Could someone please help me with this?
Here is my views/opportunities/show.html.erb page:
<%= render #opportunity %>
<form>
<fieldset>
<legend>Links</legend>
<div id="links">
<%= render #opportunity.links %>
</div>
<%= render :file => 'links/new' %>
</fieldset>
</form>
Here is my views/links/new page:
<%= form_for ([#opportunity, #opportunity.links.new]) do |f| %>
<div class="field">
<%= f.label "Description:" %> <br />
<%= f.text_field :description %>
</div>
<div class="field">
<%= f.label "URL:" %> <br />
<%= f.text_field :link_url %>
</div>
<div class="actions">
<%= f.submit 'Add' %>
</div>
<% end %>
Here is my create link controller:
def create
#opportunity = Opportunity.find(params[:opportunity_id])
#link = #opportunity.links.new(link_params)
if #link.save
redirect_to #opportunity, notice: 'link has been added'
else
redirect_to #opportunity, alert: 'Unable to add link'
end
end
Here is my Link model:
class Link < ActiveRecord::Base
belongs_to :opportunity
end
Here is my Opportunity model:
class Opportunity < ActiveRecord::Base
has_many :links
end
Here is the code from my console:
Started GET "/opportunities/7?utf8=%E2%9C%93&authenticity_token=ZLgPz98w2MjTChzzDXJ8EcqNmYNtBUG5DSYcp1CXReU%3D&link%5Bdescription%5D=testlink&link%5Blink_url%5D=testlink&commit=Add" for 127.0.0.1 at 2014-06-02 15:19:06 -0400
Processing by OpportunitiesController#show as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ZLgPz98w2MjTChzzDXJ8EcqNmYNtBUG5DSYcp1CXReU=", "link"=>{"description"=>"testlink", "link_url"=>"testlink"}, "commit"=>"Add", "id"=>"7"}
Opportunity Load (0.2ms) SELECT "opportunities".* FROM "opportunities" WHERE "opportunities"."id" = ? LIMIT 1 [["id", 7]]
Rendered opportunities/_opportunity.html.erb (2.0ms)
Link Load (0.1ms) SELECT "links".* FROM "links" WHERE "links"."opportunity_id" = ? [["opportunity_id", 7]]
Rendered links/_link.html.erb (0.2ms)
Rendered links/new.html.erb (2.0ms)
Well, you need to debug it. I would start with adding the gem pry to your project.
Then you should temporarily change your code to something like:
def create
#opportunity = Opportunity.find(params[:opportunity_id])
#link = #opportunity.links.new(link_params)
if #link.save
redirect_to #opportunity, notice: 'link has been added'
else
binding.pry
redirect_to #opportunity, alert: 'Unable to add link'
end
end
In order to test it, you perform the add link action as usual. The code processing will stop at the line where binding.pry is being called. I assume here that the saving of therecord fails and we enter else part of the condition.
If I assume correctly, the link variable will be enriched with errors. In the console prompt of binding.pry just try to display them, if there are any (#link.errors)
I hope that helps.
I have Realization model:
# encoding : utf-8
class Realization < ActiveRecord::Base
attr_accessible :city, :street, :title, :work, :photo, :date
has_attached_file :photo
end
Controller:
# encoding : utf-8
class RealizationsController < ApplicationController
before_filter :admin_required, :except => [:index,:show]
# GET /realization/new
def new
#realization = Realization.new
#realization.date = Time.now.__send__(:to_date).to_s
end
# POST /realization
def create
#realization = Realization.new(params[:realization])
if #realization.save
redirect_to #realization, notice: 'realization was successfully created.'
else
render action: "new"
end
end
(...) others
View of form:
<%= form_for #realization, :html => { :multipart => true } do |f| %>
<% if #realization.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#realization.errors.count, "error") %> prohibited this realization from being saved:</h2>
<ul>
<% #realization.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br />
<%= f.text_field :title %>
</div>
(...)
<div class="field">
<%= f.file_field :photo %>
</div>
<div class="actions">
<%= f.submit "Submit" %>
</div>
<% end %>
And routes :
resources :realizations
And WEBrick server info is that:
Started POST "/realizacje" for 127.0.0.1 at 2013-04-12 12:26:35 +0200
Processing by RealizationsController#index as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"zK5jP4ChBBY+R21TjrZkp4xGvCHViTFJ+8Fw6Og28YY=", "realization"=>{"title"=>"wwwwww", "street"=>"", "city"=>"", "work"=>"", "date"=>"2013-04-12"}, "commit"=>"Submit"}
(1.0ms) SELECT COUNT(*) FROM "realizations"
Realization Load (2.0ms) SELECT "realizations".* FROM "realizations" ORDER BY created_at DESC LIMIT 7 OFFSET 0
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Rendered realizations/index.html.erb within layouts/application (156.0ms)
Completed 200 OK in 340ms (Views: 333.0ms | ActiveRecord: 4.0ms)
While I use the form and push the submit it redirects/randers realizations/index without notice or errors even!
I have completely no idea why? Especialy that it worked before...
Maybe javascript added later on may be the reason?
Paperclip works well in update so it isn't it...
You might check your new action to see what you're passing in to the form_for.
You want to be passing in a brand new instance of your Realization model.
i.e. in the new action you should have a line that reads #realization = Realization.new
The reason I suggest this is because form_for calls a method (#new_record?) on the object you give it and will submit a post or put request depending on whether that method call returns true or false.
I'm trying to display errors messages in my ajax form (the code is based on this question):
posts_controller.rb:
def create
#post = current_user.posts.build(params[:post])
if params[:commit] == "Publish"
#post.status = "Published"
elsif params[:commit] == "Save Draft"
#post.status = "Draft"
end
respond_to do |format|
format.html do
if #post.save && #post.status == "Published"
flash[:success] = "Post published"
redirect_to #post
elsif #post.save && #post.status == "Draft"
flash[:success] = "Post saved as draft"
render 'edit'
else
render 'new'
end
end
format.js do
#post.save
end
end
end
posts/create.js.erb:
<% if #post.errors.any? %>
alert('There are errors.');
<%= render :partial=>'js_errors', :locals=> { :target=> #post } %>
<% else %>
$('.busy').html('Saved.');
<% end %>
js_errors.js.erb:
<% target.errors.full_messages.each do |error| %>
$('.busy').append('<p><%= escape_javascript( error ) %></p>');
<% end %>
posts/new.html.erb:
<%= form_for(#post, remote: true, :html => { :multipart => true }) do |f| %>
<%= render 'fields', f: f %>
<div class="form-actions">
<%= f.submit "Publish", class: "publish btn btn-primary pull-left" %>
<%= f.submit "Save Draft", class: "save-draft btn btn-default pull-left" %>
<div class="busy pull-left">
</div>
</div>
<% end %>
But for some reason nothing displays (.busy always remain empty).
In the console I can see that js_errors.js.erb is being displayed:
Started POST "/posts" for 127.0.0.1 at 2013-01-04 18:02:18 +0800
Processing by PostsController#create as JS Parameters: {"utf8"=>"✓",
"authenticity_token"=>"Qfn6HsPPDxyB1t4bM/OQKPbJ/aoAMkp74y0Z6xkoXCY=",
"post"=>{"title"=>"", "content"=>"", "tag_list"=>""},
"_wysihtml5_mode"=>"1", "commit"=>"Save Draft"} User Load (0.7ms)
SELECT "users".* FROM "users" WHERE "users"."remember_token" =
'ljl0ZsuoiHg0Jilz8bgy-g' LIMIT 1 (0.2ms) begin transaction
(0.2ms) rollback transaction Rendered posts/_js_errors.js.erb
(3.8ms) Rendered posts/create.js.erb (7.5ms) Completed 200 OK in
25ms (Views: 11.2ms | ActiveRecord: 1.0ms | Solr: 0.0ms)
What could be the problem?
(I do see the validation messages if I remove remote:true from the form).
EDIT:
I noticed alert('There are errors.'); is not being triggered. Strange.
It looks like a naming problem. You're asking to render the partial js_errors, which would be called _js_errors.js.erb; but you say your file is actually called js_errors.js.erb (no leading underscore).
Try adding the underscore and see if that helps matters.
I have been facing a similar problem a few days ago. I used remote => true option in my form to use Ajax in my Rails 3 application. After that, I have been looking for solution for validating my form fields. After trying a good number of jQuery / Javascript approaches (none of them worked for me though) I came to know about a superb gem called client_side_validations. It is very easy to install by following the instructions on github link (https://github.com/bcardarella/client_side_validations). It works like charm for client side validation of form fields, an awesome gem indeed. Hope this helps with people who are tired of looking for a simple solution for client side validation of model fields after using Ajax in Rails 3 application.