Rails: Is it possible to write view helpers with HAML syntax? - ruby-on-rails

During refactoring it would be quite handy just to copy part of HAML template and paste it to helper's code. Currently in such cases 1) I have to rewrite that part of view from scratch 2) I have to use that verbose syntax like content_tag or haml_tag.
I know that it's possible to define partials with HAML systax that will serve as helper. Though 1) as for me it's inconvinient to create a separate file for each small tiny function 2) invocation syntax for partial is quite verbose.
Ideally i'd like my *_helper class to look like this:
- def some_helper(*its_args)
.some_class
= some_ruby_expression
%some_tag#some_id
- another_expression do
etc
or at least like this:
define_haml_helper :some_helper, [:arg1, :arg2], %{
.some_class
= some_ruby_expression
%some_tag#some_id
- another_expression do
etc
}
Is there a plugin that solves my issue?
Alternatively, maybe you can describe how do you refactor HAML snippets to reusable elements (helpers/functions/partials/builders/etc)?

From the reference:
def render_haml(code)
engine = Haml::Engine.new(code)
engine.render
end
This initiates a new Haml engine and renders it.

If all you are after is a method for small reusable snippets, how about partials with local variables? http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials

Haml now has a capture_haml method that you can use to accomplish this.
def some_helper
capture_haml do
.some_class
= yield
#some-code-after
end
end
some_helper do
%h1 Hello World
end
=> <div class="some_class">
<h1>Hello World</h1>
</div>
<div id="some-code-after"></div>
Here is a link with more info on capture_haml:
http://haml.info/docs/yardoc/Haml/Helpers.html#capture_haml-instance_method

I used heredoc for such purposes:
def view_helper
Haml::Engine.new(<<~HAML).render
.example
#id ID
.desc Description
HAML
end
This way has a lot of issues with a scope of variables, so, as mentioned above, the much more correct way is to use partials for this.
UPD1: here is a solution on how to solve issues with scope:
def view_helper
Haml::Engine.new(<<~HAML).render(self)
.form
= form_tag root_path do
= submit_tag :submit
HAML
end
UPD2: even better solution(founded on the internet):
def render_haml(haml, locals = {})
Haml::Engine.new(haml.strip_heredoc, format: :html5).render(self, locals)
end
def greeting
render_haml <<-HAML
.greeting
Welcome to
%span.greeting--location
= Rails.env
HAML
end

Related

How to replace erb with liquid?

I'd like to use liquid in my Rails app. I've installed the gem. In order to use in all templates, I've created a library (lib/liquid_view.rb:):
class LiquidView
def self.call(template)
"LiquidView.new(self).render(#{template.source.inspect}, local_assigns)"
end
def initialize(view)
#view = view
end
def render(template, local_assigns = {})
#view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
assigns = #view.assigns
if #view.content_for?(:layout)
assigns["content_for_layout"] = #view.content_for(:layout)
end
assigns.merge!(local_assigns.stringify_keys)
controller = #view.controller
filters = if controller.respond_to?(:liquid_filters, true)
controller.send(:liquid_filters)
elsif controller.respond_to?(:master_helper_module)
[controller.master_helper_module]
else
[controller._helpers]
end
liquid = Liquid::Template.parse(template)
liquid.render(assigns, :filters => filters, :registers => {:action_view => #view, :controller => #view.controller})
end
def compilable?
false
end
end
And added the following initialiser (config/initializers/liquid_template_handler.rb:):
require 'liquid_view'
ActionView::Template.register_template_handler :liquid, LiquidView
PS: I've followed these instructions.
Now, if rename a template file with liquid my_template.html.liquid the <%= stylesheet_link_tag 'mycss' %> stopped working, but more importantly the {{user.first_name}} variable did not print. In my controller I have #user = current_user
What am I missing?
My intention is to completely override erb with liquid in some templates, so ideally it should work like erb (in a sense that I can pass variables from the controller and simply render it in the template without using Liquid::Template.parse(#page.template) which by the way, I don't understand how it works on a file-based template.
PS: I'm also using [this] gem (https://github.com/yoolk/themes_on_rails) for separate templates. I'm not sure it does any impact on it.
PPS: I've seen this but doesn't apply as its a older version of Rails and I'm not using prepend.
PPPS: I'm using Ruby 2.2.2 and Rails 4.2
I hope this not the problem you are thinking it is . You can check the way as it was said here Github Description
Did you create a Drop to access #user?
https://github.com/Shopify/liquid/wiki/Introduction-to-Drops
Liquid is a safe template system, so we can interpret on the backend templates that are created by the user. To access anything non trivial (number, string, hashes or arrays) you need a Drop, which is a controlled interface to define what the templates can access.
This is by design and for security reasons.

Rails 4: What Goes in Controller and Model?

I have been reading and watching few videos on learning Rails 4. All tutorials has their own code so, in my views, easy to follow. I can't seem to learn anything or remember few things so I have decided to use my own code and see if I could follow instead of using their code.
So far I understand the Controller corresponds with the views:
# In my controller
def index
#x = "I love Ruby"
end
And in my views (index.html.erb)
<% = #x %> #=> I love Ruby
That simple thing would work for the index page. Now what if want to refer other method calls in that view's index, how to do that? In the controller:
def index
#x = "I love Ruby"
end
Still within the controller's class:
def languages_i_hate
languages = %w[
Perl
PHP
C#
C++ ]
end
And in my index.html.erb:
<%= These are the languages I hate to bits: #{languages_i_hate.upcase}!
I got undeclared method or variable "languages_i_hate"
How do I call method names in a webpage?
What you are trying to do here is access a controller method in the view. When you do this the controller method is accessed as if it was a helper method. Normally controller methods aren't available to be used in this way, but you can tell the controller to make them available as helpers.
See http://apidock.com/rails/ActionController/Helpers/ClassMethods/helper_method
BTW, when you have methods in the controller which aren't actions, ie don't correspond to a route/url, you should put them in a protected section, by convention at the bottom of the controller. This makes it clear to rails and to the reader that they're not actions.
def index
#x = "I love Ruby"
languages_i_hate
end
def languages_i_hate
#languages = %w[Perl PHP C# C++ ]
end
index.html.erb:
<%= "These are the languages I hate to bits: #{#languages_i_hate}" %>
According to rails convention you have to make use of Helpers. Other approach is by use of locals while rendering template.
def index
#x = "I love Ruby"
render :template => "index.html.erb", :locals =>{:languages_i_hate => languages_i_hate}
end
def languages_i_hate
languages = %w[
Perl
PHP
C#
C++ ]
end
And in my index.html.erb:
<%= These are the languages I hate to bits: #{languages_i_hate.upcase}!%>

Rails partial locals in helper

Is there a way in RoR to access current partial locals in helper. I want something like
<% render partial: 'foo', locals: { :foo: 'bar' } %>
then to be accessed in lets say ApplicationHelper:
def my_helper_method
...
my_var = ...local_assigns[:foo] # should assign 'bar'
...
end
Other way to describe the problem would be: How do I pass all the locals passed to a partial to my helper method implicitly? If I do it explicitly, there are a lot of boilerplate code, which just pass partial arguments to to a helper method, and I have so many of them.
Is it possible?
Helpers have no knowledge of local variables inside partials. Unless you explicitly pass them a parameter, you can't do what you are proposing. What you can do is take an object-oriented approach using presenters, and avoid using helpers all together.
Either make your own, as outlined in the Railscasts episodes, or use a gem like Draper. Personally, I am in favour of the "roll your own" approach because it's very simple.
Some pseudo-code to get the idea across:
class FooPresenter
def initialize(object, template)
#object, #template = object, template
end
def amazing_foo
#template.content_tag :div, class: 'foo' do
"#{#object.name}: Wow! this is incredible!"
end
end
end
module FooHelper
def present_foo(object)
presenter = FooPresenter.new(object, self)
yield presenter if block_given?
presenter
end
end
Just instantiate that from your view.
= present_foo(foo) do
= amazing_foo
Yay, no need to pass params.
Helpers are just modules floating around in the namespace, and frankly, much of the time they encourage bad coding practices. Presenters offer a clear OOP way of handling complex view logic. Give it a try.
Usually you would pass the parameter into the method from the view, so change your method to be:
def my_helper_method(input_param)
...
my_var = ...foo # should assign 'bar'
...
end
and call this as any other method in the view passing foo as the input_param.
you need to send param to my_helper_method
def my_helper_method(foo)
...
my_var = foo
...
end
in partial
<%= my_helper_method(foo) %>

Template path in Rails 3

Let's say, I connected the route / to WelcomeController's index action.
Inside of the index.html.erb-Template I want to display the path of the template from Rails.root upwards, ie.
<h1> We are rendering: <%= how_do_i_do_this? %></h1>
to render to
<h1> We are rendering: app/views/presentation/index.html.erb</h1>
In Rails 2 I could access template.path, but this doesn't work anymore
Any ideas?
Because of how template rendering works in Rails, you will now be able to use __FILE__ for this instead. This works for me:
<%= __FILE__.gsub(Rails.root.to_s, "") %>
There may be a better way to do this however, but I couldn't find it when I went looking.
Ryan's answer works. If you also want to put your method in a helper, use Kernel#caller. Here is a method I'm using to do something similar:
def has_page_comment? code = nil
if code.nil?
# grab caller file, sanitize
code = caller.first.split(':').first.gsub(Rails.root.to_s,'').gsub('.html.erb','')
end
...
end

creating dynamic helper methods in rails

I am trying to create a bunch of dynamic helper methods like these:
show_admin_sidebar
show_posts_sidebar
show_users_sidebar
So far I have this in my helper.rb file:
#spits out a partial
def show_sidebar(name, show_sidebar = true)
#content_for_sidebar = render :partial => "partials/#{name}"
#show_sidebar = show_sidebar
end
def show_sidebar?
#show_sidebar
end
In my application layout file I have this: (NB - I'm using HAML):
- if show_sidebar?
= yield(:sidebar)
This allows me to say the following in my views:
- show_sidebar(:foo)
- show_sidebar(:bar)
And this renders the desired partial.
The problem with this is that I can only add one sidebar per page. So, I figure I need to have dynamic methods like: show_admin_sidebar, show_foo_sidebar.
So I have tried to do this:
def show_#{name}_sidebar(show_sidebar = true)
#name = name
#content_for_#{#name}_sidebar = render :partial => "partials/#{#name}"
#show_sidebar = show_sidebar
end
and then in my layout:
- if show_sidebar?
= yield("{#name}_sidebar")
But rails does not like this at all.
I have tried almost everything I can think of in my helper file and nothing works.
The reason I am using helper methods for this is because I want my content div to be 100% page width unless there is a sidebar present in which case the main content goes into a smaller div and the sidebar content goes into it's own..
If I can't get this working, then I can easily fix the problem by just adding the partials manually but I'd like to get my head round this....
Anyone got any experience with this kind of thing?
The entire approach to this was bizarrely overcomplicated, didn't follow Rails conventions at all, nor make the slightest bit of sense, and shame on prior respondents for enabling this approach instead of helping him to simplify. My apologies for being 13 months late with the answer.
Your controller should be deciding if a sidebar is to be shown or not, and setting an instance variable #side_bar_name to either nil or a sidebar name string. Then somewhere in shared view code, probably views/layouts/application.html.erb, you would have something as simple as this:
<% if #side_bar_name %>
<%= render :partial => "partials/#{#side_bar_name}" %>
<% end %>
Or better yet:
<%= render(:partial => "partials/#{#side_bar_name}") if #side_bar_name %>
If you want to use a helper (which is not a bad idea for keeping your code DRY and readable) it would basically be the same code, just moved into the helper.
<%= side_bar_helper %>
def side_bar_helper
render(:partial => "partials/#{#side_bar_name}") if #side_bar_name
end
What the controller does is up to you. It would probably do something like this:
if session[:show_side_bar]
# maybe use cookies instead of session, or store user preference in a database
#side_bar_name = session[:side_bar_name]
end
Here is a solution for you, however I wouldn't suggest too much metaprogramming:
#Add the following snippet to the proper helper module:
['admin','user','whatever'].each do |name|
class_eval{
"def show_#{name}_sidebar(show_sidebar = true)
#name = #{name}
#content_for_#{#name}_sidebar = render :partial => 'partials/#{#name}'
#show_sidebar = show_sidebar
end"
}
end
def show_#{name}_sidebar(show_sidebar = true)
That doesn't look like valid Ruby to me. Are you parsing and evaling this yourself or just throwing that right in the file and expecting it to work?

Resources