No route matches [POST] "/maps/1/rows/new" - ruby-on-rails

I am currently trying to add a row to a map but am getting a route error:
No route matches [POST] "/maps/1/rows/new"
If I do a rake routes I see that there is a route for this in there so I am a bit confused as to why.
new_map_row GET /maps/:map_id/rows/new(.:format) rows#new
This is the form that I am using to create this row.
<%= form_for #row, method: 'post', url: new_map_row_path do |form| %>
<div class="field">
<%= form.label :timeframe %>
<%= form.text_field :timeframe %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
Rows Controller
def create
#row = #map.rows.create(params[:row].permit(:timestamp))
#row.save
respond_to do |format|
if #row.save
format.html { redirect_to #row.map, notice: 'Row was successfully created.' }
else
format.html { render :new }
end
end
end
Rows Model
class Row < ApplicationRecord
belongs_to :map
end

You shouldn't post to the new action itself, that's what renders the form. The destination for the create phase, the follow-up to new, is actually the collection path with method POST:
<%= form_for #row, method: :post, url: map_rows_path do |form| %>
That's where the create action kicks in.

Related

Rails 7: Turbo stream is showing partial only

I am having a problem with the turbo-rails gem. First I installed the latest version in my Rails 7 application. On my site, I have a select input which is wrapped in a form, with a partial below that form that shows the data. Now I want to apply a filter using the select and dynamically update the data using this turbo-rails package. My form html looks like this:
<div class="users">
<div class="filters">
<%= form_with url: '/users/load', method: :get, data: { turbo_frame: :form_response } do |form| %>
<%= render partial: "shared/select", locals: {
placeholder: 'Gender',
width: '90px',
options: #genders,
classes: 'filter',
name: 'gender',
} %>
<%= form.submit %>
<% end %>
</div>
<%= turbo_frame_tag :form_response do %>
<%= render partial: "users/user_list", locals: {
users: #users
} %>
<% end %>
</div>
In my routes, I created this get request which is forwared to a load method in my controller like this:
get '/users' => "users#index"
get '/users/load' => "users#load"
And then in my controller I have the 2 methods written like this:
class UsersController < ApplicationController
before_action :require_user
USERS_PER_PAGE = 15
def index
#genders = ["Male", "Female"]
#users = User
.limit(USERS_PER_PAGE)
.order(:creation_date).reverse_order
end
def load
#users = User
.limit(USERS_PER_PAGE)
.order(:creation_date).reverse_order
if params[:gender]
#users = #users.where(gender: params[:gender])
end
respond_to do |format|
format.html { render partial: 'users/user_list', locals: { users: #users } }
end
end
end
The problem is that when I go to this page, select a gender and hit the submit button, I get to see the user data with the correct genders, but I only see the partial loaded, so the rest of the page is gone. I can see in the network tab of developer tools in Chrome that the request headers is set to:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
I want to use the turbo-streams instead of the turbo frames, because I need to update more of these items on the same page. Why is it not rendering the content inside the page, instead of rendering the partial only?
How can this be fixed?
To answer your question, you're rendering a partial without turbo stream or turbo frame, so you're only getting a partial as response.
I think, a few examples will explain everything.
# config/routes.rb
resources :users
# app/controllers/users_controller.rb
def index
scope = User.order(created_at: :desc)
scope = scope.where(name: params[:search]) if params[:search]
#users = scope
end
"Preserve log" is quite useful when working with turbo frame and it redirects and clears the console:
https://developer.chrome.com/docs/devtools/console/reference/#persist
Turbo FRAME using GET request with HTML response
We are in index action and the form is submitting back to index.
# app/views/users/index.html.erb
# expect :users_index turbo frame in a response vvvvvvvvvvvvvvvvvvvvvvvvv
<%= form_with url: users_path, method: :get, data: { turbo_frame: :users_index } do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
# turbo frame in a response needs to match turbo frame on the current page,
# since we're rendering the same page again, we have the matching frame,
# only content inside this frame is updated.
<%= turbo_frame_tag :users_index do %>
<%= render #users %>
<% end %>
# If you render some other page, you have to wrap it in
# `turbo_frame_tag :users_index`
If you want to update the url as well, so you don't lose the search on refresh:
<%= turbo_frame_tag :users_index, data: { turbo_action: :advance } do %>
<%= render #users %>
<% end %>
Turbo STREAM using GET request with TURBO_STREAM response
You have to set data-turbo-stream="true" to send a GET stream.
# app/views/users/index.html.erb
<%= form_with url: users_path, method: :get, data: { turbo_stream: true } do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
<%= tag.div id: :users_index do %>
<%= render #users %>
<% end %>
Add turbo_stream format to respond to this request:
# app/views/users/index.turbo_stream.erb
# update content inside <div id="users_index">
<%= turbo_stream.update :users_index do %>
<%= render #users %>
<% end %>
# add another `turbo_stream` here if you'd like.
Turbo STREAM using POST request with TURBO_STREAM response
# config/routes.rb
resources :users do
# # add `search` action
# post :search, on: :collection
# i'll be lazy and post to :index
post :search, action: :index, on: :collection
end
POST form submissions are sent as TURBO_STREAM by default and it will render index.turbo_stream.erb.
# app/views/users/index.html.erb
<%= form_with url: search_users_path do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
<%= tag.div id: :users_index do %>
<%= render #users %>
<% end %>
# app/views/users/index.turbo_stream.erb
<%= turbo_stream.update :users_index do %>
<%= render #users %>
<% end %>
Test set up
Just do a simple set up:
rails --version
# Rails 7.0.4
rails new turbo-test -j esbuild
cd turbo-test
bin/rails g scaffold User name
bin/rails db:migrate
open http://localhost:3000/users
bin/dev
# app/controllers/users_controller.rb
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
# Add this line:
format.turbo_stream { render turbo_stream: turbo_stream.prepend(:users, partial: "user", locals: { user: #user }) }
format.html { redirect_to user_url(#user), notice: "User was successfully created." }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
# app/views/users/index.html.erb
# submit form
<%= render "form", user: User.new %>
# new user gets prepended here
<div id="users">
<%= render #users %>
</div>

csv uploader throwing nil or empty error

I have a site that has an a table called orders, the order model, and a csv_files_controller (separate from the orders controller).
in the model I have the following:
def self.import(csv_file)
CSV.foreach(csv_file.path, headers: true) do |row|
Order.create! row.to_hash
end
end
in the csv_files_controller I have the following:
class CsvFilesController < ApplicationController
def new
#csv_file = CsvFile.new
end
def create
#csv_file = CsvFile.new(params[:csv_file])
if #csv_file.save
Order.import
redirect_to csv_file, notice: "Orders uploaded successfully"
end
end
def show
#csv_file = CsvFile.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #csv_file }
end
end
end
my upload_form partial being rendered by a page in the orders views from csv_files views:
<%= form_for #csv_file do |f| %>
<div class="controls">
<%= f.file_field :csv_file, accept: 'csv', :class => 'btn btn-xs btn-info' %>
<%= f.submit "Upload Orders", :class => 'btn btn-xs btn-success' %>
</div>
<% end %>
my routes.rb also has
resources :csv_files
when I try to load the page for the uploading I am getting a First argument in form cannot contain nil or be empty error. I am not sure what I have done wrong for it to say that.
Now if I change the form_for to :csv_files the page will load, but it is blank; no browse for file or submit button appear, but the error goes away. Doesn't matter though if the form isn't working. I am using rails 4 so I shouldn't need he :html => {multipart: true} and even when it was there it didn't change anything.
I can't figure out why it is throwing the error.
Update 9-1-15:
I have made a ton of changes, but now I get
No route matches [POST] "/orders/upload_page"
but my rake routes shows I have set my post route to :import.
resources :orders do
collection do
post :import
get :upload_page, as: 'upload_page'
get :search, as: 'search'
get :csv_report, as: 'csv_report'
get :overdue_csv_report, as: 'overdue_csv_report'
end
end
I don't understand why it is routing post to the upload_page.
The answer was threfold:
I got rid of the csv_files controller, etc. and moved all the code to the original orders controller.
I was trying to use a form_for .... do |f| and I had to revert to form_tag .... do for the form and spell everything out to get the post to work.
code snips:
Model:
def self.import(csv_file)
CSV.foreach(csv_file, headers: true) do |row|
Order.create! row.to_hash
end
end
Controller:
def import
Order.import(params[:csv_file].tempfile)
redirect_to orders_path, notice: "Orders imported"
end
form partial:
<%= form_tag import_orders_path, multipart: true do %>
<div class="controls">
<%= file_field_tag :csv_file, accept: 'csv', :class => 'btn btn-xs btn-info' %>
<%= submit_tag "Upload Orders", :class => 'btn btn-xs btn-success' %>
</div>
<% end %>
Routes:
resources :orders do
collection do
post :import
I am now working on ensuring that the uploads are using the validations in the model as it seems to be skipping it.

Remove Paperclip Attachment from Nested Attribute (Cocoon)

I have an INCIDENT with an attached WITNESS.
I am trying to show a link to remove an attachment from a nested attribute, but my link is pulling the :id of the parent record (invoice.id) instead of the nested/child record (invoice.witness_id).
I know I'm doing something wrong in my routes or in calling the correct id number from the controller or view... any help is appreciated!
incident.rb
has_many :witnesses
accepts_nested_attributes_for :witnesses, :reject_if => :all_blank, :allow_destroy => true
witness.rb
belongs_to :incident
has_attached_file :statement
routes.rb
match 'witness/:id' => 'witnesses#remove_statement', via: [:get, :post], as: 'remove_statement'
witnesses_controller
def index
#witnesses = #incident.witnesses.all
end
def remove_statement
#witness = Witness.find(params[:id])
#witness.statement = nil
respond_to do |format|
if #witness.save
format.html { redirect_to :back, notice: 'Attachment was removed.' }
format.json { head :no_content }
else
format.html { redirect_to :back, error: 'Attachment could not be removed.' }
format.json { render json: #witness.errors, status: :unprocessable_entity }
end
end
end
private
def set_witness
#witness = #incident.witnesses.find(params[:id])
end
def witness_params
params[:witness].permit(:first_name, :last_name, :phone, :email, :statement, :incident_id)
end
_witness_fields partial
<div class="nested-fields">
<div class="form-group">
....
<%= link_to "Remove Attachment", remove_statement_path, :id => :witness_id %>
...
incidents/_form.html.erb
<%= form_for(#incident, html: { :multipart => true , class: 'form-horizontal' }) do |f| %>
<%= f.error_notification %>
<% if #incident.errors.any? %>
<div class="red">
<% #incident.errors.full_messages.each do |msg| %>
<%= msg %><hr>
<% end %>
</div>
<% end %>
.....
<!-- WITNESS SECTION -->
<div class="span6">
<hr>
<fieldset id="witnesses">
<%= f.fields_for :witnesses do |builder| %>
<%= render 'witness_fields', :f => builder %>
<% end %>
</fieldset>
<p class="links">
<%= link_to_add_association 'Add Witness/Contact', f, :witnesses, { class:"btn btn-primary" } %>
</p>
</div>
</div>
<!-- END WITNESSES SECTION -->
.....
In your _withness_fields partial, you write
<%= link_to "Remove Attachment", remove_statement_path, :id => :witness_id %>
That should be something like
<%= link_to "Remove Attachment", remove_statement_path(f.object.id) %>
So two things: the path helper remove_statement_path needs the id as a parameter, and secondly, you need to actually give it the correct id of the object for which you are currently rendering.
Please note, since you dynamically add these, for new records this will not be valid (since there is no idea).
So you will have to check if the record is a new_record? and only show that link if it is not (because then you will have a valid id). If it is not a new record, you can just use the cocoon helper to remove it.

Rails form adding empty entry via model

I am new to ruby, trying to follow the official documentation and create a basic form for creating a post:
<%= form_for #post, :url => { :action => "create" }, :html => {:class => "nifty_form"} do |f| %>
<%= f.text_field :title %>
<%= f.text_area :entry, :size => "60x12" %>
<%= f.submit "Create" %>
<% end %>
The form is successfully adding an entry to the database, but an empty one, I think I must be missing something in my controller? Do I need to pass the variables somehow?
def create
#post = Main.create
end
A basic create action can look like this. You first initialize a new post. Depending on if it successfully saves you proceed.
# app/controllers/posts_controller.rb
class PostsController < ActionController::Base
def create
#post = Post.new(params[:post])
if #post.save
redirect_to #post, notice: 'Post has been created.'
else
render :new
end
end
end
You can shorten your form.
<%= form_for #post do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.text_area :entry, :size => "60x12" %>
<%= f.submit %>
<% end %>
You can see excellent example code along these lines when you generate a scaffold, so I would encourage you to try $ rails generate scaffold Post title body:text and learn by example.
Submitting a form passes the values entered into that form (along with some other information) to the controller as a hash called "params" - the params will contain a block labelled with the name of the form, in this case "post".
You need to use the post block from params in the creation of the new object.
def create
#post = Main.new(params[:post])
if #post.save
# handles a successful save
else
# handles validation failure
end
end
Try:
#post = Main.new(params[:post])
#post.save

How to render same form on the same page?

I'm curious to know how this is done. Lets say I have a simple Product model and on one page wanted to click a link and add a product form by AJAX. I than pop up other product forms, finish the first one and submit it and do the same to the others.
Here is the code I will use.
On the index page you can add a product form by the link, create it and see it in a list.
products/index.html.erb
<h1>Products</h1>
<%= link_to "Product", new_product_path, :remote => true %>
<div id="product_form">
<%= render 'form' %>
</div>
<ul id="products">
<%= render :partial => #products.reverse %>
</ul>
products/_form.html.erb
<%= form_for(#product, :remote => true) do |f| %>
<%= f.text_field :name %>
<%= f.text_field :price %>
<%= f.submit %>
<% end %>
products/_product.html.erb
<%= content_tag_for(:li, product) do %>
<p><%= product.name</p>
<p><%= product.price %></p>
<% end %>
ProductsController
def index
#products = Product.all
#product = Product.new
end
def create
#product = Product.new(params[:product])
respond_to do |format|
if #product.save
format.html { redirect_to products_url }
format.js
else
format.html { render action: "index" }
format.js
end
end
end
When it gets created it should show the product in the _product partial.
products/create.js.erb
$('#products').prepend('<%= escape_javascript(render(#product)) %>');
The link when clicked will make the product form appear in the <div id="product_form">
products/new.js.erb
$("#product-form").html("<%= escape_javascript(render(:partial => 'products/form', locals: { product: #product })) %>");
Now this generates one product form but I want to know the code logic behind rendering other product forms on the same page. How would this be done?
Usually I do this with a second object representing the collection of products. This can be an activerecord if it fits your business logic (something like a ProductCategory, or a ShoppingCart) or a simple ActiveModel "Products" with a save method that would save each of its related products.
Active Presenter can give you more details of this mechanism but I wouldn't use that gem since it's activity is quite low.

Resources