I have a nav menu with 2 tabs/links in the show.html.erb file, in UsersController.rb, I would like to use ajax to render different partial for the tabs.
In the show.html.erb I have a div named profile-data where I want to show the content.
So I do something like this:
The link structure:
<li><%= link_to "College friends", college_friends_path, :remote => true %></li>
<li><%= link_to "Highschool friends", highschool_friends_path, :remote => true %></li>
I define the routes:
match "college_friends" => "users#college_friends", :as => "college_friends"
match "highschool_friends" => "users#highschool_friends, :as => "highschool_friends"
And I define in my UserController.rb the necessary methods:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def college_friends
respond_to do |format|
format.js
end
end
def highschool_friends
respond_to do |format|
format.js
end
end
end
Last thing we have the JS files:
*college_friends.js.erb*
$('#profile-data').html("<%= escape_javascript(render(:partial => 'college_friends')) %>");
*highschool_friends.js.erb*
$('#profile-data').html("<%= escape_javascript(render(:partial => 'highschool_friends')) %>");
The partial code: _college_friends.html.erb
<% groups = #user.friends.group_by(&:college_name) %>
<% sorted_groups = groups.sort_by{|key, values| values.count}.reverse %>
<% sorted_groups.each do |collegename, friends| %>
<% next if collegename.blank? %>
<div class="contentbox">
<div class="box-header">
<h3><%= collegename %></h3>
<div class="meta-info">
<p><i class="icon-map-marker"></i> Malmö</p>
<p><i class="icon-user"></i><span class="count"> <%= friends.count %></span> vänner</p>
</div>
</div>
<ul class="friends-list">
<% friends.map do |friend| %>
<li><%= image_tag(friend.image) %>
<% end %>
</ul>
</div>
<% end %>
Nothing happens when I click the the links, and get this error in the console:
Started GET "/universitet_friends" for 127.0.0.1 at 2012-07-29 01:53:39 +0200
Processing by UsersController#universitet_friends as JS
Rendered users/_universitet_friends.html.erb (1.6ms)
Rendered users/universitet_friends.js.erb (3.1ms)
Completed 500 Internal Server Error in 7ms
ActionView::Template::Error (undefined method `friends' for nil:NilClass):
1: <% groups = #user.friends.group_by(&:college_name) %>
2: <% sorted_groups = groups.sort_by{|key, values| values.count}.reverse %>
3: <% sorted_groups.each do |collegename, friends| %>
4: <% next if collegename.blank? %>
app/views/users/_universitet_friends.html.erb:1:in `_app_views_users__universitet_friends_html_erb___1983680250475155079_70236040373720'
app/views/users/universitet_friends.js.erb:1:in `_app_views_users_universitet_friends_js_erb__1317362850668628869_70236044930260'
app/controllers/users_controller.rb:19:in `universitet_friends
Any help would be appreciated.
ActionView::Template::Error (undefined method `friends' for
nil:NilClass):
This is telling you that your #user variable is nil. This happened because when you went back to your controller for the AJAX request, you never actually set the #user variable. It does not persist between requests. You need to pass that variable during the ajax request. One way to do it is to add a user_id param to the ajax URL.
May be you can do something like this too
<%=link_to "Highschool friends", college_friends_path(:user_id => #user.id), :remote => true %>
The best way to do however would be to pass the user_id param to ajax url..you can fire the ajax request on link "on-click" event.
Related
I'm tying to do an Ajax request on a delete method for active storage files so my page won't reload.
I have two controllers: 'project_steps' (i'm using wicked gem) and 'projects'.
My view: project_steps/fourth_step.html.erb
<% if #project.supporting_docs.attached? %>
<div id="remove_file">
<%= render partial: "existing_files", :locals => {project: #project} %>
</div>
<% end %>
My partial: project_steps/_existing_files.html.erb
<% #project.supporting_docs.each do |file| %>
blah blah
<%= link_to 'Remove', delete_file_attachment_project_url(file.signed_id),
method: :delete, remote: true, class: "btn btn-sm btn-danger" %>
<% end %>
My projects_controller:
def delete_file_attachment
file = ActiveStorage::Blob.find_signed(params[:id])
file.attachments.first.purge
respond_to do |format|
format.js
end
end
projects/delete_file_attachment.js.erb:
$('#remove_file').html("<%= j render(partial: 'project_steps/existing_files', :locals =>
{project: #project}) %>")
My Routes:
resources :projects do
member do
delete :delete_file_attachment
end
end
scope 'projects/:project_id' do
resources :project_steps
end
My Error
ActionView::Template::Error (undefined method `supporting_docs' for nil:NilClass):
3: <strong>You have attached the following files:</strong>
4: </div>
5: <br>
6: <% #project.supporting_docs.each do |file| %>
7: <div class="row">
8: <div class="col">
My delete works fine and I see why the error is there but i'm wondering how can I make Ajax work and what i'm doing wrong? Happy to provide as much code as needed! Ty.
P.S if anyone would like to suggest a solution other than going through partial you feel might be better by all means!
When using partials with locals, you access the variable without the #, i.e. to access the variable in project_steps/_existing_files.html.erb you need to use project instead of #project:
project_steps/_existing_files.html.erb
<% project.supporting_docs.each do |file| %>
blah blah
<%= link_to 'Remove', delete_file_attachment_project_url(file.signed_id),
method: :delete, remote: true, class: "btn btn-sm btn-danger" %>
<% end %>
Please also note that using #project in your projects/delete_file_attachment.js.erb doesn't work if you don't set the #project variable in the controller action delete_file_attachment, i.e. you'd probably need to add a line along #project = Project.find ... to your delete_file_attachment method.
See the Layouts and Rendering in Rails guide for more informations about using locals in partials.
I have a classes User and Company, I want to re-use the users partial as the to render company staff.
In my CompaniesController I have:
def staff
#company=Company.find(params[:id])
#users=#company.works_fors.paginate(page: params[:page], :per_page => 10)
#title=#company.name+" staff."
end
And in my staff.html.erb template I have:
<% if #users.any? %>
<ul class="users follow">
<%= render #users %>
</ul>
<%= will_paginate %>
<% end %>
This is the works_fors/_works_for partial:
<%= render :partial => 'user' %>
Which Renders
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
<% if current_user.developer? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</li>
However this throws an error on the user object as it cant find the method
undefined local variable or method `user' for~~
I think this is because Im calling the user object from within companies but there is a defined relationship, or do I need to redefine in companies ?
It's hard to tell, but it appears that what you call #users in your controller is in fact not a User collection, but a WorkFor collection.
#users = #company.works_fors...
What you mean is:
#works_fors = #company.works_fors...
This means that staff.html.erb is working with a works_for collection. So you should rename the variable in your template to avoid confusion.
# staff.html.erb
<% if #works_fors.any? %>
<ul class="users follow">
<%= render #works_fors %>
</ul>
<%= will_paginate #works_fors %>
<% end %>
Now we know we are rendering a works_for partial. So an instance of works_for is be available inside the partial. We need to ask it for its associated user instance, and pass it to the render method.
# works_fors/_works_for.html.erb
<%= render works_for.user %>
As a bonus, you can save yourself some queries by preloading the users.
#works_fors = #company.works_fors.includes(:user)...
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.
I have a header partial linked to my application.html.erb that looks like this:
<header class="unselectable">
<h2 class="float_left">
<% if #user.try(:errors).present? %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
</h2>
<nav class="round">
<ul>
<% if logged_in? %>
<li><%= link_to "Home", current_user %></li>
<li><%= link_to "Settings", edit_user_path %></li>
<li><%= link_to "Log out", logout_path %></li>
<% else %>
<li><%= link_to "Log in", login_path %></li>
<% end %>
</ul>
</nav>
</header>
This is all well and good unless the page that loads doesn't have an #user variable (such as an about or logout page) in which case i get this:
undefined method `errors' for nil:NilClass
How can I make this work? I tried changing the logic to render the title unless #user.errors.any?but that didn't work either. I'm sure this is a simple fix but I can't figure it out!
EDIT added the fixes suggested (updated in the header partial above) and now get this error:
No route matches {:action=>"edit", :controller=>"users"} which seems to be coming from the edit_user_path
You can use the method .try(:something):
<% if #user.try(:errors).present? %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
If #user is nil, the .try(:errors) will not raise an error.
The .present? method works for nil too:
.
>> nil.present?
#=> false
>> false.present?
#=> false
>> [].present?
#=> false
>> ''.present?
#=> false
>> 'bonjour'.present?
#=> true
>> ['bonjour'].present?
#=> true
.present? is a combination of .nil? AND .empty?
.present? is actually the opposite result of .blank?
I highly question the need for #user in your partial which is rendered in your application layout, hence its need in every page of your application. I argue that this is not good design at all because now you're relying on a global variable in all views of your application.
I think what you really mean to use is the flash. In which case you want something like this in application.html.erb.
<% flash.each do |key, value| %>
<%= content_tag :div, value, class: key %>
<% end %>
This should be set in the appropriate controller action before it's view is rendered so that the error message displys according to the request that was just made.
If your error messages come from your models, then this should be part of what actually generates these error messages. Typically this is a call to either create or update actions in the controller. In which case you should have the error_messages partial rendered with the form when your validations do not pass and the form is rendered again with the model object.
<%= form_for #user do |f| %>
<%= render 'shared/error_messages', :object => f.object %>
<!-- and so on -->
<% end %>
This way you can be confident that the #user object is always available for the partial to render without any errors since we're explicitly passing the object to the partial itself, and the partial is being rendered with the correct context. Using #users in your partial itself is the equivalent of using a global variable, hence the entire application relying on that global variable to exist.
The #user object is now accessed with a local variable in the partial as object (or whatever your decide to end up naming it).
<% object.errors.full_messages.each do |message| %>
<li>* <%= message %></li>
<% end %>
You can reformulate to like this:
<header>
<h2 class="float_left">
<% if #user.try(:errors).try(:any?) %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
</h2>
...
</header>
Or add errors_any? to model:
class User
def errors_any?
self.try(:errors).try(:any?)
end
end
And to this:
<header>
<h2 class="float_left">
<% if #user.try(:errors_any?) %>
<%= render 'shared/error_messages' %>
<% else %>
<%= #title %>
<% end %>
</h2>
...
</header>
I have a nav menu with 2 tabs/links in the show.html.erb file, in UsersController.rb, I would like to use ajax to render different partial for the tabs.
In the show.html.erb I have a div named profile-data where I want to show the content.
So I do something like this:
The link structure:
<li><%= link_to "College friends", college_friends_path, :remote => true %></li>
<li><%= link_to "Highschool friends", highschool_friends_path, :remote => true %></li>
I define the routes:
match "college_friends" => "users#college_friends", :as => "college_friends"
match "highschool_friends" => "users#highschool_friends, :as => "highschool_friends"
And I define in my UserController.rb the necessary methods:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def college_friends
respond_to do |format|
format.js
end
end
def highschool_friends
respond_to do |format|
format.js
end
end
end
Last thing we have the JS files:
*college_friends.js.erb*
$('#profile-data').html("<%= escape_javascript(render(:partial => 'college_friends')) %>");
*highschool_friends.js.erb*
$('#profile-data').html("<%= escape_javascript(render(:partial => 'highschool_friends')) %>");
The partial code: _college_friends.html.erb
<% groups = #user.friends.group_by(&:college_name) %>
<% sorted_groups = groups.sort_by{|key, values| values.count}.reverse %>
<% sorted_groups.each do |collegename, friends| %>
<% next if collegename.blank? %>
<div class="contentbox">
<div class="box-header">
<h3><%= collegename %></h3>
<div class="meta-info">
<p><i class="icon-map-marker"></i> Malmö</p>
<p><i class="icon-user"></i><span class="count"> <%= friends.count %></span> vänner</p>
</div>
</div>
<ul class="friends-list">
<% friends.map do |friend| %>
<li><%= image_tag(friend.image) %>
<% end %>
</ul>
</div>
<% end %>
So I solve this problem but checking the source in and out. And I notice that the js files was not loaded in the app.
So I changed this:
<%= javascript_include_tag :defaults %>
To this:
<%= javascript_include_tag "application" %>
And it loaded all the necessary js files.
But then I get this error:
ActionView::Template::Error (undefined method `friends' for nil:NilClass):
You are getting
ActionView::Template::Error (undefined method `friends' for nil:NilClass):
Because
<% groups = #user.friends.group_by(&:college_name) %>
requires #user to be set.
As Brandon suggested, try
def college_friends
#user = User.find(params[:id])
respond_to do |format|
format.js
end
end
BUT
To achieve this, you need to change your link routes to include user id, similar to this
match "college_friends/:id" => "users#college_friends", :as => "college_friends"
and in your links
<li><%= link_to "College friends", college_friends_path(#user), :remote => true %></li>
As suggested in the comments above, you should have two partials named _college_friends.html.erb and _highschool_friends.html.erb
These will contain your HTML, which is what you're wanting to load via $('#profile-data').html()
Template Error:
You are not defining #user in college_friends and highschool_friends. So your view is requesting #user from that action, which isn't there.