View specific stylesheets in Sinatra (by naming convention) - ruby

I would like to have my Sinatra app include a view specific stylesheet in the layout.
Consider this simple app:
app_folder_root/
| my_app/
| my_app.rb
| public/
| css/
| index.css
| layout.css
| views/
| index.haml
| layout.haml
config.ru
config.ru:
require 'rubygems'
require './my_app/my_app'
map '/' do
run MyApp
end
app.rb:
require 'sinatra/base'
class MyApp < Sinatra::Base
get '/' do
haml :index
end
end
I tried setting a variable in my_app.rb that sets the name of the view and tried to reference it in layout.haml, but that did not work (I probably wouldn't have went with this as a final solution to the problem since I felt this was a code smell, but was just trying different possibilities).
This is using Haml, but I am hoping that is irrelevant - thinking it should be the same for erb, etc.
In layout.haml, I would like to reference the view that will be rendered and include a view specific stylesheet by a naming convention. For example, if index.haml is going to render, I would like to include css/index.css. What is the best way to go about doing this?

I solved this by doing the following:
In index.haml (at the top) I created or appended an array named views:
- #views = Array.new unless defined? #views
- #views << 'index'
In layout.haml I reference #views:
%head
- #views.each do |view|
- haml_tag :link, {:rel => 'stylesheet', :type => 'text/css', :href => "css/#{view}.css"}
I am a little disappointed with having to check #views in the view to make sure it is defined before appending to it, but for now it is manageable.
EDIT: Solved the problem with having to check if #views is defined in each view. In config.ru add the following:
before do
#views = Array.new
end
I can now remove this line from the views:
- #views = Array.new unless defined? #views

Related

How to request separate folder view path based on controller name in Sinatra?

Here's the contents of my app/controllers/application_controller.rb:
require 'sinatra/base'
require 'slim'
require 'colorize'
class ApplicationController < Sinatra::Base
# Global helpers
helpers ApplicationHelper
# Set folders for template to
set :root, File.expand_path(File.join(File.dirname(__FILE__), '../'))
puts root.green
set :sessions,
:httponly => true,
:secure => production?,
:expire_after => 31557600, # 1 year
:secret => ENV['SESSION_SECRET'] || 'keyboardcat',
:views => File.expand_path(File.expand_path('../../views/', __FILE__)),
:layout_engine => :slim
enable :method_override
# No logging in testing
configure :production, :development do
enable :logging
end
# Global not found??
not_found do
title 'Not Found!'
slim :not_found
end
end
As you can see I'm setting the views directory as:
File.expand_path(File.expand_path('../../views/', __FILE__))
which works out to be /Users/vladdy/Desktop/sinatra/app/views
In configure.ru, I then map('/') { RootController }, and in said controller I render a view with slim :whatever
Problem is, all the views from all the controllers are all in the same spot! How do I add a folder structure to Sinatra views?
If I understand your question correctly, you want to override #find_template.
I stick this function in a helper called view_directory_helper.rb.
helpers do
def find_template(views, name, engine, &block)
views.each { |v| super(v, name, engine, &block) }
end
end
and when setting your view directory, pass in an array instead, like so:
set :views, ['views/layouts', 'views/pages', 'views/partials']
Which would let you have a folder structure like
app
-views
-layouts
-pages
-partials
-controllers
I was faced with same task. I have little experience of programming in Ruby, but for a long time been working with PHP. I think it would be easier to do on it, where you can easily get the child from the parent class. There are some difficulties. As I understand, the language provides callback functions like self.innereted for solving of this problem. But it did not help, because I was not able to determine the particular router in a given time. Maybe the environment variables can help with this. But I was able to find a workaround way to solve this problem, by parsing call stack for geting caller class and wrapping output function. I do not think this is the most elegant way to solve the problem. But I was able to realize it.
class Base < Sinatra::Application
configure do
set :views, 'app/views/'
set :root, File.expand_path('../../../', __FILE__)
end
def display(template, *args)
erb File.join(current_dir, template.to_s).to_sym, *args
end
def current_dir
caller_class.downcase!.split('::').last
end
private
def caller_class(depth = 1)
/<class:([\w]*)>/.match(parse_caller(caller(depth + 1)[1]))[1]
end
def parse_caller(at)
Regexp.last_match[3] if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
end
end
The last function is taken from here. It can be used as well as default erb function:
class Posts < Base
get '/posts' do
display :index , locals: { variables: {} }
end
end
I hope it will be useful to someone.

Sinatra method to set layout

I want to write a method in sinatra to set the layout, something like
def admin_layout
set :layout, 'admin/layout'
end
I know I can do things like set :erb, layout: :'main/layout' or specify the layout for each action like
get 'admin/login' do
erb :'admin/login', layout: :'admin/layout'
end
but I'm wondering if there is a way to just abstract that into a method so I don't need to set the layout for each route. I am making an app with a main site and then an admin site, but the admin part is very lightweight, just logging in and being able to edit posts so I am not trying to get too crazy but my current file structure is like this:
db/
models/
public/
views/
admin/
main/
app.rb
config.ru
Define a layout argument:
def admin_layout
{:layout, 'admin/layout'}
end
Then you can use this method as a param like
get 'admin/login' do
## do ......
erb :'admin/login', admin_layout
end
Or if you want to judge which layout it will use, change the admin_layout function like:
def admin_layout
if request.path.start_with?('/admin')
{:layout, 'admin/layout'}
else
{:layout, 'layout'}
end
end
I know I can do things like: set :erb, layout: :'main/layout'
No you can't do that--or at least erb will ignore it because it's not an erb specific option. If you could do that, wouldn't that do what you are asking for here:
but I'm wondering if there is a way to just abstract that into a
method so I don't need to set the layout for each route.
The method already exists--template():
#Templates:
application_layout = <<END_OF_HAML
%html
%head
%title My App
%body
=yield
END_OF_HAML
template :layout do #Creates a template named :layout
application_layout
end
template :page1 do
'%div.greet Hello World!' #=> <div class="greet">Hello World!</div>
end
template :page2 do
'%div#first_name John' #=> <div id="first_name">John</div>
end
#Routes: -------------------
get '/page1' do
haml :page1 #If there is a template named :layout, then the :page1 template will be inserted into the :layout template automatically.
end
get '/page2' do
haml :page2 #If there is a template named :layout, then the :page1 template will be inserted into the :layout template automatically.
end
Then you can override the default layout, like this:
admin_layout = <<END_OF_HAML
%html
%head
%title Admin Only
%body
=yield
END_OF_HAML
template :special_layout do #Creates a template named :special_layout
admin_layout
end
get '/page3' do
haml :special_layout, :layout => false do #Don't use default layout, i.e the :layout template, instead use :special_layout template
haml :page3
end
end
See "Named Templates" here: http://www.sinatrarb.com/intro.html#Named%20Templates

How can I use views and layouts with Ruby and ERB (not Rails)?

How can I use views and layouts with Ruby and ERB (not Rails)?
Today i'm using this code to render my view:
def render(template_path, context = self)
template = File.read(template_path)
ERB.new(template).result(context.get_binding)
end
This works very well, but how can I implement the same function, but to render the template inside a layout? I want to call render_with_layout(template_path, context = self), and so that it will have a default layout.
Since you tagged it with Sinatra I assume that you us Sinatra.
By default you view is rendered in your default layout called layout.erb
get "/" do
erb :index
end
This renders your view index with the default layout.
If you need multiple layouts you can specify them.
get "/foo" do
erb :index, :layout => :nameofyourlayoutfile
end
* If you don't use Sinatra you may want to borrow the code from there.
If you use the Tilt gem (which I think is what Sinatra uses) you could do something like
template_layout = Tilt::ERBTemplate.new(layout)
template_layout.render {
Tilt::ERBTemplate.new(template).render(context.get_binding)
}
If you are using Sinatra so it has a good docimentation and one of the topics it's nested layouts (see Sinatra README)
Also good idea to use special default layout file (layout.haml or layout.erb in your view directory) This file will be always use to render others. This is example for layout.haml:
!!!5
%html
%head
##<LOADING CSS AND JS, TILE, DESC., KEYWORDS>
%body
=yield ## THE OTHER LAYOUTS WILL BE DISPALYED HERE
%footer
# FOOTER CONTENT
Thanks for all the answers!
I solved it finally by doing this, I hope someone else also can find this code useful:
def render_with_layout(template_path, context = self)
template = File.read(template_path)
render_layout do
ERB.new(template).result(context.get_binding)
end
end
def render_layout
layout = File.read('views/layouts/app.html.erb')
ERB.new(layout).result(binding)
end
And I call it like this:
def index
#books = Book.all
body = render_with_layout('views/books/index.html.erb')
[200, {}, [body]]
end
Then it will render my view, with the hardcoded (so far) layout..

HAML Pass partial dynamically to layout in Sinatra app

I am trying to build a Sinatra app with HAML and use a layout to be able to break my site down into partials:
layout.haml
!!!
%html
%head
= partial :head
%body
= partial :header
= partial :#{#template}
= partial :footer
Where my Sinatra app is calling layout.haml like so:
get '/test' do
#template = "\"test/index\""
haml :"layout"
end
To try to pull in:
views/
|----test/
|----_index.haml
Which gives me the error:
wrong number of arguments (0 for 1..2)
I have also tried redefining a number of different combinations with no success:
#template = ":\"test/index\""
= partial #{#template}
#template = "test/index"
= partial :#{#template}
Note: I am using Sinatra Partials
Am I going about this entirely the wrong way? My brain is really suffering trying to figure out how to employ HAML for such a basic concept of DRY templating.
Ah it was so simple afterall.. With the help of this question, I realized now of course that everything is wrapped with layout.haml and I simply needed to place the appopriate yield statement.
layout.haml
!!!
%html
%head
= partial :head
%body
= partial :header
= yield
= partial :footer
And call the template as usual:
get '/test' do
haml :"test/index"
end

Call Sinatra erb from another class

I need to render a Sinatra erb template inside a class in my controller. I'm having issues calling this though. I've looked in the Sinatra rdocs and have come up with this:
Sinatra::Templates.erb :template_to_render
When I do this, I get the following error:
undefined method `erb' for Sinatra::Templates:Module
Is there a way to call this from another class?
To imitate rendering behavior of Sinatra controller in some other class (not controller) you can create module like this:
module ErbRender
include Sinatra::Templates
include Sinatra::Helpers
include Sinatra::ContentFor
def settings
#settings ||= begin
settings = Sinatra::Application.settings
settings.root = "#{ROOT}/app"
settings
end
end
def template_cache
#template_cache ||= Tilt::Cache.new
end
end
Here you may need to tune settings.root
Usage example:
class ArticleIndexingPostBody
include ErbRender
def get_body
erb :'amp/articles/show', layout: :'amp/layout'
end
end
This will properly render templates with layouts including content_for
why you don't require 'erb' and after use only erb
## You'll need to require erb in your app
require 'erb'
get '/' do
erb :index
end
You could have your class return the template name and render it in the main app.
Of course that's not exactly an answer (I don't have enough rep to add a comment with this account) and you're probably doing just that by now anyway...

Resources