Sinatra: put some of my helpers in separate folder - ruby

I have a Sinatra app and I have some helpers and they have their folder (helpers) in which I have website_helpers.rb and so on. I want to move some of this helpers in their own folder inside the helpers folder: to have helpers/subhelpers, bacause the files I want to put in the subhelpers folder are different from the others and it makes sense to have a different folder for them.
I tried adding this to my config.ru
Dir.glob('./helpers/subhelpers/*.rb').each { |file| require file }
And then in the controller I have:
helpers MyHelperFromSubHelpers
but I get an error uninitialized constant (NameError).
Any ideas how to fix this so that to have a clear structure?

TBH, it sounds like you're overdoing it, there's always Rails if you want to go in for a more Java like every-namespace-must-be-a-directory layout. Aside from that, usually, helpers in separate files are placed in the Sinatra namespace - see http://www.sinatrarb.com/extensions.html#extending-the-request-context-with-sinatrahelpers
Personally, I put them in:
project-root/
lib/
sinatra/
name-of-extension.rb
Mainly because if the extension is really useful, I'll end up wanting to use it in another project, and this is the standard layout for a Sinatra gem, which makes it easy to extract it into one and with barely any change in the calling code.
Dir.glob will only return the file name and not the full path with each match, so you need to add the path:
Dir.glob('./helpers/subhelpers/*.rb').each do |file|
require File.expand_path File.join( File.dirname(__FILE__), "./helpers/subhelpers/", file)
end
will probably fix it.

Related

Extending Faker in a gem, where do I put the YAML file?

I have a gem which uses Faker to help build mock data. I'd like to add a new class that generates a new category of stuff, using the same syntax Faker itself uses. The first half is easy, I simply define the class, and make sure my gem loads the file:
# lib/faker/restaurant.rb
module Faker
class Restaurant < Base
class << self
def name
parse('restaurant.name')
end
end
end
end
So far, so good. Now, to describe what values can come out of this, I create a YAML file:
faker:
en:
restaurant:
suffix: [Cafe,Restaurant]
name:
- "#{Name.first_name}'s #{suffix}"
So, the actual question: Where does this file go, and what name should it have? If this were a Rails application, it would be config/locales/faker.en.yml. In a gem, that doesn't appear to work - there isn't actually a 'config' directory, for one thing, but creating it for this purpose doesn't help, I get:
> Faker::Restaurant.name
I18n::MissingTranslationData: translation missing: en.faker.restaurant.name
Ok, figured it out. Special thanks to dax, whose comments prodded me in the right direction.
Faker uses the I18n gem for localization (which is why the YAML files live in a 'locales' directory). This means that I needed to add my custom YAML to the I18n load path. It doesn't matter exactly where the files are, as long as they're added to the load path. In my case, I put it at lib/faker/locales/en-US.yml, and added that entire directory to the load path, thus:
lib/my_gem.rb:
I18n.load_path += Dir[File.join(File.expand_path(File.dirname(__FILE__)), 'faker/locales', '*.yml')]
require "faker/restaurant"
Any .yml files I put in that directory should be loaded and available to Faker.
Side note: I also needed to change the YAML format slightly - it should be
en:
faker:
<stuff>
rather than with faker at the top level, as I had it.

i18n markdown files in Rails 3 views

I am currently working through Michael Hartl's Rails Tutorial while experimenting with some other things not covered in the book. After completing Chapter 5, where the static pages are created, I decided change the view code to HAML, internationalize the pages, and put the static content into separate (non-partial) Markdown files, using the RDiscount gem to render them. For example:
app/views/static_pages/about.html.haml
- provide(:title, t('.about_us'))
:markdown
#{render file: "static_pages/about.#{params[:locale]}.md"}
Under the static_pages directory, I have Markdown files like about.en.md, about.it.md, about.ja.md etc, so interpolating in the :locale parameter is what determines which language Markdown file gets rendered.
My questions are:
The static_pages directory is a bit crowded with Markdown files, so are there any sensible default/best practice locations (perhaps outside of the app directory) to keep these Markdown files, where they could be presumably be edited by people who don't need to know about the inner workings of the app?
What better ways are there to implement rendering multi-lingual Markdown files in views? My use of :locale and the double string-interpolation seems inelegant.
Is there a way to change this code so that I can pass Ruby variables into the Markdown file? I know I can, for example, use a #{language} variable in the Markdown by just changing about.en.md into a HAML partial (_about.en.html.haml) and change the code to look something like:
app/views/static_pages/about.html.haml
- provide(:title, t('.about_us'))
:markdown
#{render "about.#{params[:locale]}", language: 'Markdown!'}
But, is there a way to do this without changing the Markdown file into another type of file? If such a way exists, is it recommended/feasible?
After having a look at this StackOverflow answer, it seemed that the best location for i18n Markdown files would be their own action name directories under the config/locales directory, and that there was a good opportunity to refactor the render code on all views for the StaticPagesController. So, using about.html.haml as an example below, the call to render in the home, help, about, and contact views has been changed to the exact same code:
app/views/static_pages/about.html.haml
- provide(:title, t('.about_us'))
:markdown
#{render file: localized_page_for(action_name, params[:locale])}
The localized_page_for method is defined in the StaticPagesHelper:
app/helpers/static_pages_helper.rb
module StaticPagesHelper
def localized_page_for(action, locale)
"#{Rails.root}/config/locales/#{action}/#{action}.#{locale.to_s}.md"
end
end
So, now all the Markdown files have been taken out of the app/views/static_pages directory and are called from their respective logical directories (eg. config/locales/about/about.en.md etc) using ActionController's action_name attribute and the locale, making for less clutter.
As for question 2 above, string-interpolation seems to be common enough for this kind of problem, so I'll consider it "elegant" enough as well.
As for question 3 above, after exhaustive searching, I haven't found a way anyone has passed in variables in to a pure Markdown file, and the documentation doesn't seem to say anything about supporting them, so I'm going to conclude that it's not possible. If passing Ruby variables in to Markdown is absolutely necessary, the file will need to be run through another interpreter, kind of like is described in this StackOverflow answer.
Update:
After running security scanner Brakeman against the app, it came up with a potential Dynamic Render Path security warning (albeit a weak one) due to dynamically passing in params[:locale] to the render call instead of passing it a static string. So, I moved the call to the localized_page method out of the views, moved the method itself out of the StaticPagesHelper (so that file is now empty) and in to the StaticPagesController and then instantiated a #page instance variable in each method to pass to the view. In summary, the code now looks like this, which doesn't get the security warning:
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
before_filter :localized_page, only: [:help, :about, :contact]
def home
if signed_in?
#micropost = current_user.microposts.build
#feed_items = current_user.feed.paginate(page: params[:page])
else
localized_page
end
end
def help
end
def about
end
def contact
end
private
def localized_page
#page = "#{Rails.root}/config/locales/"\
"#{action_name}/#{action_name}.#{params[:locale].to_s}.md"
end
end
app/views/static_pages/about.html.haml
- provide(:title, t('.about_us'))
:markdown
#{render file: #page}

render individual file in middleman

I am writing a helper and I need to get a rendered file as String.
I see that the method that I need exists in the middleman's library: http://rubydoc.info/github/middleman/middleman/Middleman/CoreExtensions/Rendering/InstanceMethods#render_individual_file-instance_method
How do I call this function from my helper class?
I tried:
require "middleman-core/lib/middleman-core/core_extensions/rendering.rb"
...
puts Middleman::CoreExtensions::Rendering::InstanceMethods.render_individual_file(filepath)
But it does not seem to find the file, any idea?
I'm not sure 3.0 beta is quite ready for primetime.
That said, it does sound like the partial method is what you're looking for.
Unless I'm missing something, the Middleman method seems like an overly-complex solution. For one of my sites I wanted to load entire text files into my templates, so I wrote this helper:
# Shortcut for loading raw text files. Obviously assumes that given file is in a valid format.
# #return [String] File contents
def load_textfile(filename)
File.read filename.to_s
end
Also, you should clarify if you are intending to use this within a template, or within Ruby code. It's not clear to me based on your question.
Here is an example of how one would use above helper:
Currently of note, Middleman is in the process of transitioning to version 4, and the conventions for loading helpers will change. The most straightforward way to define a helper is within a helper block in your config.rb file, as follows:
helpers do
# Define helper functions here to make them available in templates
end
I use Slim for templating. It really is the best. In slim you would appply helper as thus:
= load_textfile 'path'
p You can embed helper output in your page with interpolation, too: #{load_textfile 'path'}

Partial HAML templating in Ruby without Rails

I really don’t need the overhead of Rails for my very small project, so I’m trying to achieve this just using just plain Ruby and HAML.
I want to include another HAML file inside my HAML template. But I haven’t found a good—or really usable—way of doing this.
For example, I have these two HAML files:
documents.haml
%html
%body
= include(menu.haml) body
%article …
menu.haml
%ul
%li
%a whatever …
Include is obviously not the way to go here. But it does a nice job describing what I’m trying to achieve in this example.
I totally recommend the Tilt gem for these things. It provides a standard interface for rendering many different template langages with the same API, lets you set custom scope and locals, lets you use yield, and is robust and fast. Sinatra is using it for templates.
Example:
require 'haml'
require 'tilt'
template = Tilt.new('path/to/file.haml')
# => #<Tilt::HAMLTemplate #file="path/to/file.haml" ...>
layout = Tilt.new('path/to/layout.haml')
output = layout.render { template.render }
This lets you yield inside the layout to get the rendered template, just like Rails. As for partials, David already described a simple and nice way to go.
But actually, if what you're writing is going to be served over HTTP, i suggest you take a look at Sinatra, which already provides templating, and has the simplest request routing you could imagine.
I've done this before, just for a quick-and-dirty template producer. The easiest way is to just render the HAML inside the parent object:
%p some haml that's interesting
= Haml::Engine.new('%p this is a test').render
%p some more template
You'll more than likely want to build some methods to make this easier--a couple of helper methods. Maybe you write one called render_file that takes a filename as an argument. That method might look something like:
def render_file(filename)
contents = File.read(filename)
Haml::Engine.new(contents).render
end
Then, your template would look more like:
%p some template
= render_file('some_filename.haml')
%p more template
Note, you will probably need to pass self to the original Haml::Engine render so that it knows how to find your render_file method.
I've had great success just using the instructions posted by David Richards in a concatenated way, without variables, like this:
= Haml::Engine.new(File.read('/Volumes/Project/file_to_include.haml')).render
There's obviously a more elegant way. But for someone who just wants to include one or two files, this should work nicely. It's a drawback that all base files using these includes have to be recompiled after some changes to the latter. It might be worthwile to just use php include if the environment allows that.
def render_file(filename)
contents = File.read('views/'+filename)
Haml::Engine.new(contents).render
end
(Adding this semi-redundant answer to show how one might incorporate the techniques from other answers.)
Include something like this in your setup code to monkey-patch the Haml library.
module Haml::Helpers
def render_haml(filename)
contents = File.read(Rails.root.join("app", "assets", "templates", filename))
Haml::Engine.new(contents).render
end
end
Then in your Haml file
.foo
= render_haml('partial.haml')
.bar
Obviously this is a Rails-ish example (because I wanted to use HAML in my asset pipeline instead of as views)... You will want to replace Rails.root.join(...) with a way to find filename in your project's templates or partials directory.

How to check if a template exists in Sinatra

In the Sinatra ruby framework, I have a route like this:
get '/portfolio/:item' do
haml params[:item].to_sym
end
This works great if the template that exists (e.g., if I hit /portfolio/website, and I have a template called /views/website.haml), but if I try a URL that doesn't have a template, like example.com/portfolio/notemplate, I get this error:
Errno::ENOENT at /portfolio/notemplate
No such file or directory - /.../views/notemplate.haml
How can I test and catch whether the template exists? I can't find an "if template exists" method in the Sinatra documentation.
Not sure if there is a Sinatra specific way to do it, but you could always catch the Errno::ENOENT exception, like so:
get '/portfolio/:item' do
begin
haml params[:item].to_sym
rescue Errno::ENOENT
haml :default
end
end
The first answer is not a good one because if a file does not exist a symbol is created anyway. And since symbols are not garbage collected you're easily leaking memory. Just think of a ddos attack against nonexisitng files that create symbols all the time. Instead use this route here (taken from one of my projects routing css files):
# sass style sheet generation
get '/css/:file.css' do
halt 404 unless File.exist?("views/#{params[:file]}.scss")
time = File.stat("views/#{params[:file]}.scss").ctime
last_modified(time)
scss params[:file].intern
end

Resources