Easy breadcrumbs for RESTful rails application - ruby-on-rails

Is there any helper method (Other than default rails breadcrumb) that generates bread crumb navigation dynamically for a particular page without having to pass trivial parameters in RESTful application? That is, something that figures out automatically where the user is based on the REST url she is visiting?
For above mentioned implementation, we need to pass parameters like
REST
<% add_crumb(‘Profile’, user_profile_path) %>
Current page
<% add_crumb(“My Incoming Messages”, request.path) %>
There must be a way to generalize the code so that no parameter passing is required and should work for all RESTful apps with minimal configuration.

Developed a simple hack. The method however assumes that there exists a method 'name' for every model object corresponding to each resource in the RESTful url. Whatever that the method 'name' returns is shown as breadcrumb name. If it is not found, it is shown as it is without making it link to anything. Separator used is '->' You may change it to suit your requirement.
def get_bread_crumb(url)
begin
breadcrumb = ''
sofar = '/'
elements = url.split('/')
for i in 1...elements.size
sofar += elements[i] + '/'
if i%2 == 0
begin
breadcrumb += "<a href='#{sofar}'>" + eval("#{elements[i - 1].singularize.camelize}.find(#{elements[i]}).name").to_s + '</a>'
rescue
breadcrumb += elements[i]
end
else
breadcrumb += "<a href='#{sofar}'>#{elements[i].pluralize}</a>"
end
breadcrumb += ' -> ' if i != elements.size - 1
end
breadcrumb
rescue
'Not available'
end
end
The method generally accepts request.url (Which given url of the current page) as the parameter. The method purposefully accepts the url for customization purposes. To generate the breadcrumb, simply add following code in your view -
<%= get_bread_crumb(request.url) %>
For the url /ideabox/2/idea/1, the bread crumb looks like
alt text http://www.imagechicken.com/uploads/1234855404069992300.png
Excuse me if code quality is not that great. I'm sure this code can be re-factored but I'm also sure you would be able to do that before using it.
Thanks.

The solution provided by chirantan is great. If you need breabcrumbs for namespaced controller and need to change the breadcrumbs depending on the namespace as well then try this. This is not perfect but refactor it as you need. It works for my project.
Define a new helper: navigation_helper.rb
module NavigationHelper
def navigation_add(title, url, namespace)
if defined? ##namespace and !##namespace.nil? and ##namespace == namespace
##navigation ||= []
else
##navigation = []
end
##navigation << {title: title, url: url} unless title == "Home"
new_nav = []
##navigation.each do |hash|
new_nav.push hash
if hash[:title].to_s == title.to_s
break
end
end
##navigation = new_nav
##navigation.uniq!
##namespace = namespace
end
def render_navigation
if (request.path_parameters[:controller].sub('/', '::_').camelize + 'Controller').classify.constantize.action_methods.to_a.include? 'index'
navigation_add controller_name.camelize.to_s, request.path_parameters.merge({action: 'index'}).except(:id), params[:controller].include?('/') ? params[:controller].split("/").first : nil
end
if defined? ##navigation
render partial: 'navigation/navigation', locals: { navs: ##navigation, namespace: ##namespace }
else
render text: ''
end
end
end
Then define a view for this helper _navigation.haml
- unless navs.blank?
%ol.breadcrumb
- navs.each_with_index do |nav, index|
- if index == 0
%li=link_to fa_icon('arrow-left', text: 'Go Back'), :back
- unless namespace.nil?
%li
%h4.inline= request.fullpath.split('/')[1].gsub('-', '_').camelize
= fa_icon('angle-double-right')
%li= link_to_unless (nav[:title] == controller_name.camelize and action_name == 'index'), fa_icon(nav[:title].downcase.singularize, text: nav[:title]), nav[:url]

Related

Converting a string into a controller method call

I'm trying to create a generic breadcrumbs method in my application controller to assign the breadcrumbs based on the current controller. If I wanted the breadcrumbs for the index of 'Thing', I would need in the view:
<%= breadcrumb :things, things %>
And for edit or show:
<%= breadcrumb :thing, thing %>
Where things is a method in the things controller that returns all things, and thing is a method returning the relevant thing.Both are exposed, and I have in my application layout:
<%= breadcrumb crumb, crumb_resource %>
And in my application controller:
def crumb
return controller_name.singularize.to_sym if edit_or_show_action
controller_name.to_sym
end
def crumb_resource
resource = controller_name
resource = controller_name.singularize if edit_or_show_action
end
def edit_or_show_action
action_name == 'edit' || 'show'
end
This obviously returns a string for crumb_resource, rather than the call to the controller method. From what I can find I believe it has something to do with send, however
controller.send(resource)
obviously doesn't work. How can I convert the string that is returned into a controller method call?
If you're using Gretel, then I think what you might be looking for is this:
def crumb_resource
resource = controller_name
resource = controller_name.singularize if edit_or_show_action
self.instance_variable_get("##{resource}")
end
This is assuming you have stored the relevant resource into #resource_name during the edit/show/index action.
I accepted the answer given as I'm assuming it works for people using instance variables to access models in their view, however in the end this worked for me:
breadcrumb crumb, eval(crumb_resource)
where eval evaluates the string, basically reverse interpolation which sounds pretty cool.

Intercepting template content with ActionView

I have a Haml template with a bunch of “custom tags” scattered around, e.g. {{REPLACE_ME}}. Before rendering this template, I am going through the template and replacing all occurrences using Ruby’s String#gsub.
That has not been a problem—I have simply created a custom template handler, which essentially acts as a middleman by first modifying the template source and then passing it on to the Haml template handler.
For each occurrence of one of these “custom tags” in the view, it adds to a numerator that is stored in the background. The problem that I am facing, however, is that the numerator is being bumped up for content that will never be displayed to the user, e.g. <% if false %>{{SECTION_NO}}<% end %>.
Such content as above should be filtered out before I replace the content. I seem, however, to have hit a wall, as the Haml (and ERB) handlers return a content buffer and not the “end/finished” content.
Is there a straight-forward solution to this issue?
Here is an example of a view:
- if true
%h1 {{SECTION_NO}}
- if false
%h1 {{SECTION_NO}}
- if true
%h1 {{SECTION_NO}}
So basically, I need a way to filter out the middle (false-statement) of the view source before replacing and numerating occurrences of “{{SECTION_NO}}”.
For reference, here is the gist of the template handler I created for this purpose:
class ActionView::Template::Handlers::Caml
def initialize
#section = 0
#paragraph = 0
#references = {}
end
def call(template)
rendered_haml = ActionView::Template.registered_template_handler(:haml).call(template)
updated_source = render_sections(rendered_haml)
updated_source = render_paragraphs(updated_source)
updated_source = render_references(updated_source)
return ActionView::Template.new(
updated_source,
template.identifier,
template.handler,
{
locals: template.locals,
virtual_path: template.virtual_path,
updated_at: template.updated_at
}
)
end
private
def render_sections(source)
source.gsub(/{{section:([a-zA-Z0-9-_]+)}}/).each do |match|
fail 'Key already exists.' if #references.key?($1)
#section += 1
#references[$1] = "#{#section}"
"#{#section}"
end
end
def render_paragraphs(source)
# ...
end
def render_references(source)
source.gsub(/{{([a-zA-Z0-9-_]+)}}/).each do |match|
#references.fetch($1)
end
end
end
ActionView::Template.register_template_handler(
:caml,
ActionView::Template::Handlers::Caml.new
)

How to add views for custom member action in active admin?

I have to add view for custom action which is member action, and want to display association records on it. Is there way to add custom view instead of just adding html.erb in admin's view folder?
I dont want to add or create html files but by using the active admins helpers.
The member action in nothing else like a controller action, thats mean you can do the same things in it.
You can use thinks like:
render text: "Hello world!"
Or if you want a complex markup:
message1 = "Hello"
#message2 = "world!"
view = Arbre::Context.new(message: message, self) do
h1 do
span message
span #message
end
end
render body: view.to_html # or .to_s
You can use the following code for the render html for rails 4.1:
render html: '<html><body>Some body text</body></html>'.html_safe ## Add html_safe
But, if you use rails 4.2, so you can use the following:
render text: '<html><body>Some body text</body></html>'
I think the following answer is very useful for your question.
Ended up with adding the following in html.erb file in admin/user/messages.html.erb
<% view = Arbre::Context.new({messages: #messages, user: #user}, self) do
panel "Sent Messages" do
paginated_collection(messages, download_links: false) do
table_for collection do
column :id
column :content
end
end
end
end
%>
<%= view.to_s %>
Depending on your namespace (ActiveAdmin is on /admin in my case) you can create the folder app/views/admin in the same way you would in the rest of your application.
For example, if you have a resource User and an action apply_discount
ActiveAdmin.register User do
member_action :apply_discount, method: [:get, :put] do
if request.get?
render :apply_discount
else
# TODO ...
end
end
end
you could put your ARBRE view file into app/views/admin/users/apply_discount.html.arb -> notice the extension is ARB, not ERB - though ERB should work too according to the docs

current_page? wrong after POST

I'm displaying certain items in my navigation bar depending on the current page. When I go to my sign in page the correct items are displayed. If I sign in with an incorrect password the items change and are incorrect.
In my html I check if (current_page?(new_user_session_path))
After the incorrect password is submitted and the page reloads this condition isn't returning true and it's displaying the wrong items in the navbar. I looked through the requests on the server logs and I'm guessing it's because the second time around the page loads after a POST (the unsuccessful password submission). Is there a different path I need to check for the second time?
Expanding on Scott's answer, you could create a helper in app/helpers/navigation_helper.rb for instance, like so:
module NavigationHelper
def current_location?(*args)
options = args.extract_options!
options.each do |key, val|
return false unless eval("controller.#{key.to_s}_name") == val
end
true
end
end
And use it this way:
current_location?(controller: 'my_controller', action: 'new')
current_location?(controller: 'my_controller')
current_location?(action: 'new')
In your view you can then do something like:
# Change this according what your really need
if current_location?(controller: 'sessions', action: 'new')
Hope it helps ; )
If you look at the source code of current_page?, it always returns false if the request's HTTP mode is anything other than GET or HEAD:
http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-current_page-3F
def current_page?(options)
unless request
raise "You cannot use helpers that need to determine the current " "page unless your view context provides a Request object " "in a #request method"
end
return false unless request.get? || request.head?
...
So even if your incorrect form is at exactly the same path as new_user_session_path, your logic won't match.
You may want to consider comparing controller.controller_name and controller.action_name directly instead. Not exactly elegant, but it's going to be more reliable.

Ruby on Rails. link_to doesn't raise an exception

Why does Rails create the path to the current page in the href atribute of the anchor element instead of raising an exception if I pass to the link_to method an instance variable which isn't associated with any resource (and equals nil)?
Here's an example:
Routes
# app/config/routes.rb
Example::Application.routes.draw do
resource :example
end
HAML
-# app/views/examples/show.html.haml
%div
= link_to 'Non-existent resource', #ne_resource
HTML
<!-- http://localhost/example -->
<div>
Non-existent resource
Thanks.
Debian GNU/Linux 6.0.1;
Ruby 1.9.2;
Ruby on Rails 3.0.6.
If you take a look at the link_to method it links to the url using the url_for method.
def link_to(*args, &block)
if block_given?
options = args.first || {}
html_options = args.second
link_to(capture(&block), options, html_options)
else
name = args[0]
options = args[1] || {}
html_options = args[2]
html_options = convert_options_to_data_attributes(options, html_options)
url = url_for(options) #THIS GETS CALLED
href = html_options['href']
tag_options = tag_options(html_options)
href_attr = "href=\"#{html_escape(url)}\"" unless href
"<a #{href_attr}#{tag_options}>#{html_escape(name || url)}</a>".html_safe
end
end
URL for
def url_for(options = {})
options ||= {}
url = case options
when String
options
when Hash #THIS CASE IS TRUE
options = options.symbolize_keys.reverse_merge!(:only_path => options[:host].nil?)
super
when :back
controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
else
polymorphic_path(options)
end
url
end
From the above, you can see that url_for is valid without options or with a nilClass, it is not designed to raise an exception. If you want errors when using link_to, then make sure to use the dynamic "path" helper methods, in the above case, new_example_path.
I asked DHH (the creator of Rails) a closely related question about link_to's behavior when passed nil, nil arguments. I was hoping it would just not render an tag at all, instead of making me check for nils before calling it. He graciously replied:
https://twitter.com/dhh/status/198487578352156672
The essence of this answer applies to your question. It needs to do something when it is handed a nil. Gazler points out what's technically happening, but DHH's response shows a bit of the higher level "why?" The url_for method is fine with taking a nil argument, with the current page a sensible default.

Resources