I have a Rails 3 app and am writing an "administrative" partial I'd like to include in views across many different models. The partial will include resourceful links (e.g. new, index, show), but my design fails if the resource has a route (e.g. index) disabled in routes.rb.
A sample view (views/my_resources/show.html.erb):
<% provide(:title, 'My Resource') %>
<div style="float:right;">
<%= render '/shared/resource_menu' %>
</div>
<h1>My Resource</h1>
<%= #my_resource.name %>
Excerpt from /shared/_resource_menu.html.erb:
<div class="submenu">
<b><%= File.basename(params[:controller]).pluralize.titleize %></b><br />
<% if params[:action] != 'index' %>
<%= link_to 'Index', url_for(:controller => params[:controller], :action => 'index', :only_path => true) %><br />
<% end %>
<% if params[:action] != 'new' %>
<%= link_to 'New', url_for(:controller => params[:controller], :action => 'new', :only_path => true) %><br />
<% end %>
</div>
Currently, I've hacked it so that each conditional looks like:
<% if params[:action] != 'index' && !#omit_index_in_resource_menu %>
and I set the #omit_index_in_resource_menu by hand in the relevant controllers, but that's clunkier than I would prefer.
Can I instead do something more elegant like:
<% if params[:action] != 'index' && controller.actions.include?('index') %>
Maybe this would help you:
UsersController.action_methods (it's a class method)
or
UsersController.new.available_action? :index (it's an instance method)
More info: http://api.rubyonrails.org/classes/AbstractController/Base.html#method-i-action_methods
Related
In my rails app I have a model Song. On one of the user's profile pages user_music_path(#user) I'm rendering all of their songs.
People can also like these songs, which is set up and working, I just want to use AJAX instead of page refreshes. The problem I'm having is when using ajax, I get the error undefined local variable or method "song" when I'm clicking the link "like".
Here is some of my code:
User's Music Page views/users/music.html.erb
...
<ul class="playlist list-group">
<%= render #songs %>
</ul>
views/songs/_song.html.erb
<li class="list-group-item">
<p class="pull-right">
<%= render :partial => 'songs/like_button', :locals => {:song => song} %>
<%= render :partial => 'songs/likes', :locals => {:song => song} %>
</p>
</li>
views/songs/_like_button.html.erb
<% if current_user.voted_on?(song) %>
<%= link_to "Unlike", unlike_song_path(song), :method => :post, :remote => true %>
<% else %>
<%= link_to "Like", like_song_path(song), :method => :post, :remote => true %>
<% end %>
views/songs/_likes.html.erb
<%= song.votes.count %>
views/songs/like.js.coffee
$("p.pull-right").html('<%= render :partial => "songs/like_button", :locals => {:song => song} %><%= render :partial => "songs/likes", :locals => {:song => song} %>');
controllers/songs_controller.rb
def like
begin
#vote = current_user.vote_for(#song = Song.find(params[:id]))
#vote.save
respond_with #song.user, :location => user_music_path(#song.user)
rescue ActiveRecord::RecordInvalid
redirect_to #song
end
end
And as mentioned earlier, the :like action worked perfectly before I starting using AJAX. I've also added respond_to :html, :js to my songs_controller. And the error I get when I try to like the song is ActionView::Template::Error (undefined local variable or method "song" for #<#<Class:0x000001028de9d0>:0x00000106ecd9f0>):
Where you have
<%= song.name %>
try instead
<%= #song.name %>
And do the same with your link_to paths and your render calls.
Do rake routes and use of of them for the link, e.g. (pseudo-code) link_to 'song', song_audio_path
I am working on a app where you can add task etc. I know this is kind of weird but I just like to see how others would implement this. How would you change the following code into a helper method and use it?
The original code
<h2>My Tasks</h2>
<% task = #work.tasks %>
<% if task.empty? %>
<%= link_to 'Create a new task', new_task_path %>
<% else %>
<%= render :partial => 'notes/note', :locals => {:note => #note} %>
<% end %>
My way of doing a helper method
def task_check
task = #work.tasks
if task.empty?
link_to 'Create a new task', new_task_path
else
render :partial => 'notes/note', :locals => {:note => #note}
end
end
In my view
<%= #work.task_check %>
Personally, I wouldn't extract this out at all, this is view logic and it belongs in the views. It definitely doesn't belong in a model, but it could arguably be extracted into a helper. I'd change it slightly:
<h2>My Tasks</h2>
<% if #work.tasks.blank? %>
<%= link_to 'Create a new task', new_task_path %>
<% else %>
<%= render :partial => 'notes/note', :locals => {:note => #note} %>
<% end %>
Calling blank? instead of empty? will work even if #work.tasks is nil
.
You can't define a helper in the model. It won't have access to render, link_to or any other controller or view methods. So just define your method almost exactly as is in a file in your helpers directory, maybe application_helpers.rb or work_helpers.rb:
def task_check(work, note)
task = work.tasks
if task.empty?
link_to 'Create a new task', new_task_path
else
render :partial => 'notes/note', :locals => {:note => note}
end
end
And then call it in your view like so:
<%= task_check(work, note) %>
I'm implementing show/hide feature for users comments.
Discussed here: https://stackoverflow.com/a/10174194/439688
My aim was to:
1. Limit the default shown comments to 2.
2. Have a span with text that states the number of total comments for that particular micropost and when clicked by a user have it expand and show all comments for that micropost. I would be using Jquery/Ajax to hide, show, prepend etc.
The first change was to limit the amount of comments shown to the user and I achieved this by creating a method in my helper called "comments" and here I pass in the id of the micropost the comment belongs to.
def get_comments(micropost_id)
Comment.limit(2).order("created_at DESC").where(:micropost_id => micropost_id)
end
Now the each loop that loops through each comment will only show the 2 most recent comments.
<<% #microposts.each do |m| %>
<% if m.poster_id.nil? %>
<div class="postHolder">
<nav class="micropostOptions">
<ul class="postMenu">
<li class="deletePost"><%= link_to content_tag(:span, "Delete post"), m, :method => :delete, :confirm => "Are you sure?", :title => m.content, :class => "message_delete", :remote => true %>
</li>
<li class="disableCommenting"><%= link_to content_tag(:span, "Pause commenting"), "2" %></li>
<li class="blockCommenter"><%= link_to content_tag(:span, "Block commenter"), "3" %></li>
<li class="openInNewWindow"><%= link_to content_tag(:span, "Open in new window"), "4" %></li>
<li class="reportAbuse"><%= link_to content_tag(:span, "Report abuse"), "5" %></li>
</ul>
</nav>
<%= link_to image_tag(default_photo_for_current_user, :class => "poster_photo"), current_users_username %>
<div class="post_content">
<div class="post_container">
<div class="mainUserNameFontStyle"><%= link_to current_users_username.capitalize, current_users_username %> - <div class="post_time"> <%= time_ago_in_words(m.created_at) %> ago.</div>
</div>
<%= simple_format h(m.content) %> </div>
<div class="commentsCount">
<%= content_tag :span, pluralize(m.comments.count, 'comment'), :class => "view_all_comments" if m.comments.any? %>
</div>
<% if m.comments.any? %>
<% comments(m.id).each do |comment| %>
<div class="comment_container">
<%= link_to image_tag(default_photo_for_commenter(comment), :class => "commenter_photo"), commenter(comment.user_id).username %>
<div class="commenter_content"> <div class="userNameFontStyle"><%= link_to commenter(comment.user_id).username.capitalize, commenter(comment.user_id).username %> - <%= simple_format h(comment.content) %> </div>
</div><div class="comment_post_time"> <%= time_ago_in_words(comment.created_at) %> ago. </div>
</div>
<% end %>
<% end %>
<% if logged_in? %>
<%= form_for #comment, :remote => true do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.hidden_field :micropost_id, :value => m.id %>
<%= f.text_area :content, :placeholder => 'Post a comment...', :class => "comment_box", :rows => 0, :columns => 0 %>
<div class="commentButtons">
<%= f.submit 'Post it', :class => "commentButton", :disable_with => "Post it" %>
<div class="cancelButton"> Cancel </div>
</div>
<% end %>
<% end %>
</div>
</div>
From here this is where it gets confusing for me. I got slightly further using link_to but then decided I'd prefer not to have the url to the comments count show in the browser status bar. This is why I switched to using span.. but now it's not quite easy to do what I wish to do as I can't use the link_to/remote => true now.
How do I make it so when a user clicks the comment count span an ajax call is made pointing to:
def load_comments
#load_comments = Comment.where(:micropost_id => params[:id])
respond_to do |format|
format.js { render :load_comments }
end
end
I thought about putting a click function in users.js but how would I pass the params of the micropost that is in the each loop in the code above into users.js? I don't think it's possible.
All my comment posting is done via ajax but because I used forms for these it was so much easier for me to just add remote => true and create some js templates and do something on success of ajax post.
Not sure if I'm even going about this the right way. I'd appreciate some help/advice from more experienced rails programmers.
Kind regards
Rails partial
#Display all the comments based on local passed to this partial
# Initially pass limit as 2(or whatever you want). then on click of span pass limit as nil. then you can check if limit is nil you can query the model without limit specifier.
<% #comments = Comment.custom_find(#your_params) %>
<% #comments.each do |comment| %>
<%= comment.title %>
<% end %>
javascript/jquery
function load_all_comments(id)
{
new Ajax.Updater('show_comments',
'<%=url_for(:controller => "your_controller", :action => "your_action")%>', {
parameters: {'id':id },
method: 'get',
onSuccess: function(request){
div_comments = document.getElementById("partial_comments_list");
div_comments.innerHTML = request.responseText;
}
});
} // you can call this js function on span click. use jquery if you want.
Controller:
Then inside your_action of your_controller, dont forget to render the partial
render :partial => "show_comments", :layout => false
Edit:
you can even pass locals to your partial
render :partial => "show_comments", :locals => {:post => #post}
Using this every time your partial view will get updated, on the basis of locals you pass.
of course this is just an example not a complete code/solution.
There may be better ways. but this worked fine for me.
Another option is to just output all of the comments and hide the ones you don't want to show first. <div class="hidden_comments" style="display:none;"> a comment </div>
Then just have some javascript to show them when the span is clicked?
<script type="text/javascript">
$("#span_id").click(function() {
$('.hidden_comments').show();
});
</script>
This works great if you do not don't have a ton of comments.
If you really want to do it your way, I have done it before but it gets messy.
Put this in your application.js
$('.comment_span').live('click', function () {
$.get(this.data_url, null, update_row, 'json');
return false;
});
Your span would look like this:
<span class="comment_span" data_url="http://website.com/resource/more_comments">
show all comments
</span>
This example returns the data as json, so I used the update_row function to update replace the comments data.
function update_row(data, status) {
$("#comments-table").append(data.html);
};
Here is what my controller looked like:
def more_comments
#comments = Comments.all
if #comments
respond_to do |format|
format.js {
render :json => {
:html => render_to_string(:partial => "comments"),
}.to_json
}
end
end
end
You should do this via your index action.
Pass a param to it to determine if you want to show all comments or just the current set (I'd use will_paginate to handle this.
Haven't looked too deep into your code as I'm on my phone right now, but something like this:
class CommentsController < ApplicationController
def index
If params[:show_all] == "true"
#comments = Comment.all
else
#comments = Comment.where(foo: bar).paginate(per_page: 2, page: params[:page])
end
end
Then you have it respond to JavaScript and send the page param with your Ajax request
I render an instance of a model into a partial with the following code <%= render #places %>.
I created _places.html.erb. I thought I could use the following code since the render method is supposed to iterates trhough the places collection :
<li>
<%= link_to place.name, place %>
</li>
I get this error : undefined local variable or method 'place' for #<#<Class:0x007fc1c0a4e6b8>:0x007fc1c05050a8>
I have to use to make it work :
<% #places.each do |place| %>
<li>
<%= link_to place.name, place %>
</li>
<% end%>
try this:
<%= render :partial => 'places.html', :collection => #places, :as => :place %>
Or, if you have the partial file as singular name, you can do it like this(without the variable :as)
<%= render :partial => 'place.html', :collection => #places %>
I am making a contact us form that doens't have any model. I just want it to be a popup in the home page when people click on contact us.
In the partial we load _contact.html.haml , this is what we have
<div class="contact-us-form">
<h>Contact Us</h>
<% form_tag(:controller => "application", :action => "deliver_contact_form", :method=>'post') do %>
<p>
<%= label_tag(:contact_email, "Your Email") %>
</p>
<p>
<%= text_field_tag(:contact_email) %>
</p>
<p>
<%= label_tag(:contact_detail, "Details") %>
</p>
<p>
<%= text_area_tag(:contact_detail,:"", :size=> "44x6") %>
</p>
<p>
<%= submit_tag("Submit") %>
</p>
<% end %>
<div id="contact_cancel"><%= link_to "Cancel", "javascript:void()"%></div>
</div>
and we define the action deliver_contact_form as this in application_controller.rb
def deliver_contact_form
ContactMailer.welcome_email(params).deliver
respond_to do |format|
format.html { redirect_to comments_path }
end
end
when I run just the home page (localhost:3000) I get
No route matches {:controller=>"application", :action=>"deliver_contact_form", :method=>"post", :locale=>:en}
I was wondering what I need to do?
Thanks,
mina
You need to define the route in your routes.rb
I also suggest creating a new controller, even for a small action like this, anyway:
match '/deliver_contact_form' => 'application#deliver_contact_form', :via => :post
<% form_tag(deliver_contact_form_path, :method => 'post') do