I have localised markdown strings in my language file, and I am looking for a cleaner way to do the following in HAML:
#text_for_something
:markdown
#{ t(:text_in_markown) }
Or, equivalently:
#text_for_something!= Maruku.new( t(:text_in_markown) ).to_html
I know this is not what you were thinking of but you could just add the following helper to helpers/application_helper.rb
def render_md(key)
Maruku.new( t(key) ).to_html
end
and then just use it in your HAML like this:
#text_for_something!= render_md :text_in_markown
Hope it helps.
I ended up doing something similar for the i18n of my Rails Tutorial sample app. Not sure if it matters, but I used RDiscount to render Markdown.
In summary, I file each i18n-ized markdown file under a controller/action directory under config/locales, and determine which page needs to be rendered in the controller. For example, in the case of a simple About page, here is where the :en markdown file is located here:
config/locales/static_pages/about/about.en.md
About Us
========
Some more markdown text...
The path to the file to be rendered gets determined in the controller and assigned to #page, then the file itself gets rendered out in the relevant HAML partial:
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
before_filter :localized_page
def about
# ...
end
# ...
protected
def localized_page
locale = params[:locale]
#page = "#{Rails.root}/config/locales/#{controller_name}/"\
"#{action_name}/#{action_name}.#{locale}.md"
end
end
app/views/static_pages/about.html.haml
= render 'static_page', title: t('.about_us'), page: #page
app/views/static_pages/_static_page.html.haml
- provide(:title, title) if title
:markdown
#{render file: page}
Related
In my Rails application I'd like to have a special route to download a custom PDF.
This PDF should be generated via PDFKit from an ERB template in my application. Instead of describing what I'd like to achieve, I better paste some non-executable but commented code:
class MyController < ApplicationController
def download_my_list_as_pdf
# The template uses the instance variables below
#user_id = params[:user_id]
#items = ['first_item', 'second_item']
# This line describes what I'd like to do but behaves not like I want ;)
# Render the ERB template and save the rendered html in a variable
# I'd also use another layout
rendered_html = render :download_my_list_as_pdf
kit = PDFKit.new(rendered_html, page_size: 'A4')
kit.to_pdf
pdf_file_path = "#{Rails.root}/public/my_list.pdf"
kit.to_file(pdf_file_path)
send_file pdf_file_path, type: 'application/pdf'
# This is the message I'd like to show at the end
# But using 'render' more than once is not allowed
render plain: 'Download complete'
end
end
I wasn't able to find an answer to this problem yet, any help would be greatly appreciated!
render_to_string(*args, &block)
Raw rendering of a template to a string.
It is similar to render, except that it does not set the response_body
and it should be guaranteed to always return a string.
render does not return a string it sets the response_body of the response.
class MyController < ApplicationController
def download_my_list_as_pdf
# The template uses the instance variables below
#user_id = params[:user_id]
#items = ['first_item', 'second_item']
# This line describes what I'd like to do but behaves not like I want ;)
# Render the ERB template and save the rendered html in a variable
# I'd also use another layout
rendered_html = render_string(:download_my_list_as_pdf)
kit = PDFKit.new(rendered_html, page_size: 'A4')
kit.to_pdf
pdf_file_path = "#{Rails.root}/public/my_list.pdf"
kit.to_file(pdf_file_path)
send_file pdf_file_path, type: 'application/pdf'
end
end
However if you are sending a file you cannot send text or html as well. This is not a limitation of Rails but rather how HTTP works. One request - one response.
Usually javascript is used to create notifications surrounding file downloads. But consider first if its really needed as its pretty annoying to users as the browser usually tells you that you downloaded a file anyways.
I'm looking for a way to find which render type I am currently executing from a helper. Mostly to do something like this:
# some_helper.rb
def url_to_faq
if plain_text_render
...
else
# HTML
end
end
We've used a workaround override for render in a gem that we are using, but it's gross. Is there some official way to get at the renderer metadata, either in Rails 4 or Rails 5?
I think you can use presenter here.
In controller:
#link_presenter = LinkPresenter.new(format: request.format.symbol, view: view_context)
Link presenter class:
class LinkPresenter
def initialize(format:, view:)
#format = format
#view = view
end
def url_to_faq
if format == :html
...
else
...
end
end
end
then in the view:
#link_presenter.url_to_faq
By passing view_context to presenter you get access to view helpers. If not needed, then drop it.
Nice article about Presenters: Presenters in Rails by Nithin Bekal
I want to have independent .markdown files that I then include in my haml templates. So I want to somehow include -- not render -- an external file into the template. I want the parent file to have :markdown in it, with the inclusion directly below that, and then the .markdown file to just be pure markdown.
Or: Is there a way to just use markdown as a rails template language (same way i can write templates or partials in erb or haml and rails just figures it out)?
This is similar to your solution, but using the :markdown filter. Haml does string interpolation on any filtered text, so you can read the markdown file like this.
:markdown
#{File.read(File.join(File.dirname(__FILE__), "foo.markdown"))}
You could put this into a helper, but you'd have to be careful with the file paths.
The simplest way I could think of is to create a custom template handler for Markdown. That you get to use Markdown code as partials (also getting support for locals for free).
module Markdown
class Template < ActionView::Template::Handler
include ActionView::Template::Handlers::Compilable
self.default_format = Mime::HTML
def compile(template)
'"' + Maruku.new(template.source).to_html + '".html_safe'
end
end
end
And then register it with markdown extension (in application.rb or custom initializer):
ActionView::Template.register_template_handler(:md, Markdown::Template)
And then user render like you would for any partial :)
# for file foo.md
= render 'foo'
Here's the best I can come up with (no haml filter involved at all):
=raw Maruku.new(File.read(File.dirname(__FILE__)+'/foo.markdown')).to_html
This is something I asked the HAML developers a while back. I suggested we needed an :include filter for HAML. Their response was we should load the file into a variable and then use the variable like we would any other.
Extending ActionView::Template::Handler is deprecated in at least Rails 3.1.0. Instead the following worked for me:
In lib/markdown_views.rb:
require "rdiscount"
class MarkdownViews
def call template
'md = ERB.new(<<\'EOF\'%s
EOF
).result( binding)
RDiscount.new( md).to_html.html_safe'% template.source
end
end
In config/application.rb:
require "markdown_views"
ActionView::Template.register_template_handler :markdown, MarkdownViews.new
In views/public/home.html.markdown:
# H1
+ Bullets.
+ screaming.
+ from out of nowhere
<%= "Embedded Ruby" %>
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
.js.erb's are nice, because you can use them to replace parts of a page without having to leave the current page, which gives a cleaner and unchopped up feel to the site / app.
Is there a way to use them in sinatra? or an equivalent?
Just add .js to the end of the symbol you're passing erb(). A la (to call mypage.js.erb):
erb "mypage.js".to_sym
Dirty, but it works.
Based on your description, I'm guessing that your desire is to have portions of a page editable and replaced via AJAX. If this is wrong, please clarify.
I do this in my Sinatra apps by including (my own) AJAXFetch jQuery library and writing code as shown below. This lets me use the partial both when rendering the page initially as well as when editing via AJAX, for maximum DRYness. The AJAXFetch library handles all AJAX fetch/swap through markup alone, without needing to write custom JS on the pages that use it.
helpers/partials.rb
require 'sinatra/base'
module Sinatra
module PartialPartials
ENV_PATHS = %w[ REQUEST_PATH PATH_INFO REQUEST_URI ]
def spoof_request( uri, headers=nil )
new_env = env.dup
ENV_PATHS.each{ |k| new_env[k] = uri.to_s }
new_env.merge!(headers) if headers
call( new_env ).last.join
end
def partial( page, variables={} )
haml page, {layout:false}, variables
end
end
helpers PartialPartials
end
routes/bug.rb
get '/bug/:bug_id' do
if #bug = Bug[params[:bug_id]]
# ...
haml :bug
end
end
# Generate routes for each known partial
partials = %w[ bugdescription bughistory bugtitle fixer
pain project relatedbugs status tags version votes ]
partials.each do |part|
[ part, "#{part}_edit" ].each do |name|
get "/partial/#{name}/:bug_id" do
id = params[:bug_id]
login_required
halt 404, "(no bug ##{id})" unless #bug = Bug[id]
partial :"_#{name}"
end
end
end
post "/update_bug/:partial" do
id = params[:bug_id]
unless params['cancel']=='cancel'
# (update the bug based on fields)
#bug.save
end
spoof_request "/partial/#{params[:partial]}/#{id}", 'REQUEST_METHOD'=>'GET'
end
views/bug.haml
#main
#bug.section
= partial :_bugtitle
.section-body
= partial :_bugdescription
<!-- many more partials used -->
views/_bugtitle.haml
%h1.ajaxfetch-andswap.editable(href="/partial/bugtitle_edit/#{#bug.pk}")= title
views/_bugtitle_edit.haml
%form.ajaxfetch-andswap(method='post' action='/update_bug/bugtitle')
%input(type="hidden" name="bug_id" value="#{#bug.id}")
%h1
%input(type="text" name="name" value="#{h #bug.name}")
%span.edit-buttons
%button(type="submit") update
%button(type="submit" name="cancel" value="cancel") cancel
sinatra really isn't meant to be a full stack framework. Its supposed to get you on the road very quickly. You could use an erb separately and then load into your sinatra code.