I have a problem. In my Rails app, I am trying to load a list of data using given select-option elements. When I hit submit, I am getting an error that the create method doesn't exist in my controller (which is true). I am new with this turbo-rails package, but this is what I got so far:
index.html.erb
<!DOCTYPE html>
<html>
<head>
<%= stylesheet_link_tag 'users/main', 'data-turbolinks-track': 'reload' %>
<%= csrf_meta_tags %>
</head>
<body>
<main>
<div class="grid">
<div class="users">
<div class="filters">
<form action="/users" method="POST">
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
<%= render partial: "shared/select", locals: {
placeholder: 'Gender',
width: '90px',
options: $genders,
classes: 'filter',
name: 'gender'
} %>
<button type="submit">submit</button>
</form>
</div>
<div id="user-data-list">
<%= render partial: "users/user_list", locals: {
users: #users
} %>
</div>
</div>
</div>
</main>
</body>
</html>
users.controller.rb
class UsersController < ApplicationController
before_action :require_user
USERS_PER_PAGE = 15
def index
#users = User.limit(USERS_PER_PAGE).order(:creation_date).reverse_order
end
def load
gender = if params[:gender] then params[:gender] else '' end
#users = User.limit(USERS_PER_PAGE).where(
"gender LIKE ?", "%" + gender + "%"
).order(:creation_date).reverse_order
respond_to do |format|
format.turbo_stream
format.html { redirect_to users_url() }
end
end
end
load.turbo_stream.erb
<%= turbo_stream.replace "user-data-list" do %>
<%= render partial: "users/user_list", locals: {
users: #users
} %>
<% end %>
routes.rb
Rails.application.routes.draw do
# System check
get '/health', to: 'application#health'
get '/users' => "users#index"
post '/users' => "users#load"
resources :users
post '/login' => 'auth#login'
get '/logout' => 'auth#logout'
end
_select.html.erb
<!DOCTYPE html>
<html>
<head>
<%= stylesheet_link_tag 'components/select', media: 'all', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<select
style="<%= 'width: ' + width + ';' if width %>"
<% if defined?(id) %>
id="<%= id %>"
<% end %>
<% if defined?(classes) %>
class="<%= classes %>"
<% end %>
<% if defined?(name) %>
name="<%= name %>"
<% end %>
<% if defined?(required) %>
required
<% end %>
>
<option
value=""
disabled
selected
hidden
>
<%= placeholder %>
</option>
<% options.each do |option| %>
<option><%= option %></option>
<% end %>
</select>
</body>
</html>
But when I run this code and hit the submit button I get the following error:
I don't have a create method in my controller, because there is no possible option to create a user. I have a custom load method, and I can't seem to figure out why it is trying to call the create method. Can someone explain this to me?
Routes have priority in the order they are declared.
resources :users already declares a POST /users route. While you could fix it by moving your strange route up you don't actually even need this in the first place.
What you're doing looks like you're simply adding a bunch of filters to a resource. You can do that by just sending a GET request to the collection path (the index) and having it use the optional query string parameters to apply conditions.
Rails.application.routes.draw do
# System check
get '/health', to: 'application#health'
# Only declare the routes you're actually using!
resources :users, only: [:index]
post '/login' => 'auth#login'
get '/logout' => 'auth#logout'
end
<%= form_with(url: users_path, method: :get, data: { turbo_frame: "user-data-list" }) do |form| %>
<%= render partial: "shared/select", locals: {
placeholder: 'Gender',
width: '90px',
# why is this a global?
options: $genders,
classes: 'filter',
name: 'gender',
form: form # pass the form builder along
} %>
<%= form.submit %>
<% end %>
# /app/users/index.turbo_stream.erb
<%= turbo_stream.replace "user-data-list" do %>
<%= render partial: "users/user_list", locals: {
users: #users
} %>
<% end %>
class UsersController < ApplicationController
before_action :require_user
USERS_PER_PAGE = 15
def index
#users = User.limit(USERS_PER_PAGE)
.order(:creation_date: :desc)
if params[:gender]
# and why is this pattern matching?
#users.where("gender LIKE ?", "%#{gender}%")
end
respond_to do |format|
format.turbo_stream
format.html
end
end
end
Conceptually performing a search, adding pagination or any other kinds of filters via query strings parameters are idempotent actions as its not creating or altering anything and the resource will look the same for any visitor using the same URI (in theory at least).
You should thus use GET and not POST which lets the request be saved in the browsers history - be cached, and be directly linkable.
Related
Still learning how to use Ajax in Rails.
I have a simple controller:
class PagineController < ApplicationController
def cerca
end
def trova
respond_to do |format|
format.js {render layout: false }
end
end
end
routes.rb:
get '/cerca', to: 'pagine#cerca'
post '/cerca', to: 'pagine#trova'
This is trova.js.erb:
$('#search_table').html("<%= escape_javascript(render partial: 'search_result') %>");
This is cerca.html.erb:
<%= form_tag '/cerca', remote: true do %>
<%= text_field_tag :name, params[:name], placeholder: 'Inserisci parte
del nome del gioco' %>
<%= submit_tag 'Cerca' %>
<% end %>
<h1> Results </h1>
<div id="search_table">
<p>Here the partial</p>
</div>
and this is the _search_result.html.erb
<p> I'm running well </p>
I'm expecting that, when i submit a search with the button, "Here the partial" will be substituted with "I'm running well".
But this is not happens.
In the log i see that _search_result.html.erb is correctly rendered, so all should run.
Why don't?
Ok. I've resolved (for future references if someone needs), using different js:
document.getElementById("search_table").innerHTML = "<%= j render 'search_result' %>"
I'm having the following error while trying to pass locals to the partial:
undefined method `country_code' for nil:NilClass
This is what my code looks like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Trial</title>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<script type="text/javascript">
$(document).ready(function() {
$('select#order_country_code').change(function(){select_wrapper = $('#order_state_code_wrapper');
$('select', select_wrapper).attr('disabled', true);
country_code = $(this).val();
url = "subregion_options?parent_region=#{country_code}";
$('#order_state_code_wrapper').load(url);
});
});
</script>
</head>
<body>
<%= form_for(:order) do |f| %>
<div class="field">
<%= f.label :country_code %><br />
<%= f.country_select :country_code, priority: %w(US CA), prompt: 'Please select a country' %>
<%= f.label :state_code %><br />
<%= render partial: 'subregion_select', locals: {parent_region: f.object.country_code} %>
</div>
<% end %>
</body>
</html>
Partial:
<div id="order_state_code_wrapper">
<% parent_region ||= params[:parent_region] %>
<% country = Carmen::Country.coded(parent_region) %>
<% if country.nil? %>
<em>Please select a country above</em>
<% elsif country.subregions? %>
<%= subregion_select(:order, :state_code, parent_region) %>
<% else %>
<%= text_field(:order, :state_code) %>
<% end %>
</div>
Controller:
class OrdersController < ApplicationController
layout false
def index
end
def subregion_options
render partial: 'subregion_select'
end
end
Routes:
Rails.application.routes.draw do
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
# You can have the root of your site routed with "root"
root 'access#index'
match ':controller(/:action(/:id))', :via => [:get, :post]
get 'subregion_options' => 'orders#subregion_options'
end
Thank you very much for your help :)
I think you need to add this in appropriate action in your controller :
#order = Order.new
and in view, instead
form_for(:order)
put
form_for(#order)
Add to your config/routes.rb following:
resources :orders, only: [:index]
finally it works after your advice.I also had this wrong and it was not passing parameters in the url :
url = "subregion_options?parent_region=#{country_code}";
I dont know in cofeescript but in javascript the correct way is like this :
url = "subregion_options?parent_region="+country_code;
I'm trying to put some code in a partial from my show page in /events. The code works fine when I use it in my show page, but when i extract it to a partial I'm getting a No Method Error
undefined method `event' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_PaperTrail_Version:0x007f4368823a68>
versions/_version.html.erb
<% cache version do %>
<div class="feed-item">
<h4>
<%= link_to version.item.name, version.item %>
<small>
<%= version.event + "d" %> by <%= link_to User.find(version.whodunnit).username, User.find(version.whodunnit) %>
</small>
</h4>
<ul class="list-unstyled">
<% version.changeset.each do |data| %>
<li>
<strong><%= data[0].capitalize %>:</strong>
<% if data[1][0].present? %><p class="red">- <%= data[1][0] %></p><% end %>
<p class="green">+ <%= data[1][1] %></p>
</li>
<% end %>
</ul>
</div>
<% end %>
show.html.erb
<%= render :partial => '/versions/version', :object => #versions %>
events controller
def show
#versions = #event.versions
end
events model
has_paper_trail
Any idea how i could put my code in a partial instead of having it inside my show view? Thanks.
EDIT:
I still get a No Method Error
undefined method `item' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_PaperTrail_Version:0x007f43688ee1a0>
events_controller.rb
before_action :set_event, only: [:show, :update, :destroy]
def set_event
#event = Event.find(params[:id])
end
Do you have defined the #eventvariable in the eventscontroller?
The #event.versions method has to be called on an actual event object.
For example
def show
#event = Event.find(1)
#versions = #event.versions
end
I've got this working now quite accidentally, but I don't understand what causes it to break when I explicitly specify what partials are to be used for rendering the resource/s. Can anyone explain it?
The index template for my Posts controller contained the following line, which was giving me an error:
<%= render partial: 'posts', collection: #posts %>
The error (in my browser) said:
NoMethodError in Posts#index
Showing /Users/applebum/Sites/rails_projects/eventful2/app/views/posts/_posts.html.erb where line #1 raised:
undefined method `any?' for #<Post:0x000001064b21f0>
Extracted source (around line #1):
1: <% if posts.any? %>
2: <div id="posts">
3: <% posts.each do |post| %>
4: <%= render partial: "posts/post", locals: { post: post } %>
Changing the problem line to
<%= render #posts %>
made the error disappear and the posts appear (displayed nicely in markup from the appropriate partials) as I had wanted and expected them to.
Here's my _posts.html.erb partial:
<% if posts.any? %>
<div id="posts">
<% posts.each do |post| %>
<%= render partial: "posts/post", locals: { post: post } %>
<% # render :partial => "comments/comments", :collection => post.comments %>
<% end %>
</div>
<% end %>
And the _post.html.erb partial it's referring to, if that matters:
<div class="post" id="post_<%= "#{post.id}" %>">
<div class="post_inner">
<%= link_to avatar_for(post.user, size: "small"), post.user.profile %>
<div class="post_body">
<div class="user-tools">
<% if can? :destroy, post %>
<%= link_to '<i class="fi-x"></i>'.html_safe, post, :method => :delete, remote: true, :class => "delete", :confirm => "Are you sure you want to delete this post?", :title => post.content %>
<% end %>
</div>
<h5 class="username">
<%= link_to post.user.name, post.user.profile %>
<span class="timestamp">• <%= time_ago_in_words(post.created_at) %> ago</span>
</h5>
<div class="content">
<%= post.content %>
</div>
<ul class="foot">
<li>Like<li>
<li>Share</li>
</ul>
</div>
</div>
</div>
And the relevant bits from the controller:
class PostsController < ApplicationController
respond_to :html, :js # Allow for AJAX requests as well as HTML ones.
before_filter :load_postable
load_and_authorize_resource
def index
#post = Post.new
#posts = #postable.posts
end
private #################
def load_postable
klass = [User, Event].detect { |c| params["#{c.name.underscore}_id"] } # Look for which one of these there's a ***_id parameter name for
#postable = klass.find(params["#{klass.name.underscore}_id"]) # Call find on that, passing in that parameter. eg Event.find(1)
end
Can anyone explain to me what's going on here? I couldn't find anything in the Layouts and Rendering guide at rubyonrails.org.
Thanks!
Your error comes from assuming :collection and #posts mean the same thing when rendering. From Rails Docs (point 3.4.5):
Partials are very useful in rendering collections. When you pass a collection to a partial via the :collection option, the partial will be inserted once for each member in the collection
So, if you use that, for each post, you will be doing post.any? which fails as any? isn't defined for a single post.
From the same docs, you should check if render returns Nil to see if the collection is empty:
<h1>Posts</h1>
<%= render(#posts) || "There are no posts." %>
PD: Use the partial to render only one post, not all of them.
GL & HF.
Is there a way to route devise users to a login and signup screen that doesn't output the header and footer? as everything is being yielded to my application.html.erb
Heres my current code for application.html.erb
<body>
<div id="wrap">
<%= render 'layouts/header' %>
<%= yield %>
<div id="push"></div>
</div>
<%= render 'layouts/footer' %>
</body>
Controllers support :only and :except options for layouts so you can restrict access in the controller like this:
class RandomController < ApplicationController
layout 'application', :except => [:signup, :signin]
def signin
// some code
end
def signup
// some code
end
end
I'd recommend you view the official RoR website section (this link) on rendering views.
Update 2
Set the layout for specific Devise controllers using a callback in config/application.rb.
(so this code belongs in the /config/application.rb file)
This allows for layouts to be specified on a per-controller basis. If, for example, you want a specific layout assigned to Devise::HomeController views:
config.to_prepare do
Devise::HomeController.layout "layout_for_home_controller"
end
A more indepth example using four different layouts, one for each controller:
config.to_prepare do
Devise::HomeController.layout "layout1"
Devise::UsersController.layout "layout2"
Devise::ArticlesController.layout "layout3"
Devise::TutorialsController.layout "layout4"
end
Not the prettiest solution, but this is a way of doing it.
<body>
<div id="wrap">
<%= render 'layouts/header' unless params[:controller] == "devise/sessions" or "devise/registrations" and params[:action] == "new" %>
<%= yield %>
<div id="push"></div>
</div>
<%= render 'layouts/footer' unless params[:controller] == "devise/sessions" or "devise/registrations" and params[:action] == "new" %>
</body>
EDIT:
application_helper.rb
def hidden_header_footer
params[:controller] == "devise/sessions" or "devise/registrations" and params[:action] == "new"
end
application.html.erb
<%= render 'layouts/footer' unless hidden_header_footer %>
<%= render 'layouts/header' unless hidden_header_footer %>
If you're using Devise with the standard layouts, they're render into Main view: <%= yield %>.
So you can change your application.html.erb in:
<body>
<div id="wrap">
<% if user_signed_in? %>
<%= render 'layouts/header' %>
<% end %>
<%= yield %>
<% if user_signed_in? %>
<%= render 'layouts/footer' %>
<% end %>
</div>
</body>
And your application_controller.rb in:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
#before_action :authenticate_user!, except: [:index, :home]
before_action :authenticate_user!
end