How to know in what environment the code runs? - ruby

In the layout file of haml I would like to determine whether we are in our development and build environments. We're using Middleman.
I would like to do something like this:
- if environment == 'development'
/ Development Code
= javascript_include_tag "Dev.js"
I tried to access Ruby's environment variable, as well as define a custom variable in the config.rb file with no success.

You’ve almost got it right – you need to check against a symbol rather than a string:
- if environment == :development
/ Development Code
= javascript_include_tag "Dev.js"
Middleman also adds the development? and build? methods which may be easier to use:
- if development?
/ Development Code
= javascript_include_tag "Dev.js"
This works with ERB too:
<% if development? %>
<!-- Development Code -->
<%= javascript_include_tag "Dev.js" %>
<% end %>

First, if possible, you should separate the logic from the data. Determine your environment in your controller, and toggle the data being sent to the view (HAML layout file).
Typically you'd set a flag in your environment variables and access it in your code from ENV. For instance, Sinatra makes the development/test/production setting available inside the code using their RACK_ENV symbol:
:environment - configuration/deployment environment A symbol
specifying the deployment environment; typically set to one of
:development, :test, or :production. The :environment defaults to the
value of the RACK_ENV environment variable (ENV['RACK_ENV']), or
:development when no RACK_ENV environment variable is set.
The environment can be set explicitly:
set :environment, :production
If you have to roll your own, that's a good way to go about it.

Use the :environment symbol that middleman creates by default:
http://rubydoc.info/github/middleman/middleman/Middleman/Application#environment-instance_method
combined with haml - you can do something like:
= javascript_include_tag "Dev.js" unless :environment == "developement"
note that middlemans build process changes the :environment value to "build"
you can also use developement? to test whether you're on dev or not: http://rubydoc.info/github/middleman/middleman/Middleman/Application#development%3F-instance_method
All the above applies to middleman 3.0.6 and might not work on lesser versions (won't work on 2.x for sure)

Related

Figaro - Rails Missing secret_key_base for development

I've just switched to using the Figaro gem v1.0.0 with Rails 4.1.6.
Since deleting my secrets.yml file I now get the error:
Unexpected error while processing request: Missing secret_key_base for 'development' environment, set this value in config/secrets.yml
Do i still need the secrets.yml file - isn't this the job of Figaro's application.yml file?
My application.yml file is like
development:
secret_key_base: 56....
Looking into the Railties gem at https://github.com/rails/rails/blob/master/railties/lib/rails/application.rb you can see the secrets method defined which includes a fallback for secret_key_base
def secrets #:nodoc:
#secrets ||= begin
secrets = ActiveSupport::OrderedOptions.new
yaml = config.paths["config/secrets"].first
if File.exist?(yaml)
require "erb"
all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {}
env_secrets = all_secrets[Rails.env]
secrets.merge!(env_secrets.symbolize_keys) if env_secrets
end
# Fallback to config.secret_key_base if secrets.secret_key_base isn't set
secrets.secret_key_base ||= config.secret_key_base
secrets
end
end
In config/application.rb adding the following resolves the issue
config.secret_key_base = Figaro.env.secret_key_base
I have never used Figaro gem but try these, create the config/secret.yml file and inside write:
development:
secret_key_base: <%= ENV['secret_key_base'] %>
I was just informed that as of Rails 4.1.x, config/secrets.yml does need to be uploaded to heroku. Rails will no longer look directly at its ENV in order to find its secret_key_base.
So secrets.yml needs to come off of the .gitignore file, and your project would need to be recommited and re-pushed to heroku.
(secrets.yml would still get its values from heroku's ENV, which would still be loaded up via Figaro the same way as before - figaro heroku:set -e production. Use heroku config to get a nice quick look at your ENV variables to confirm they are there)

Are my assets being served from AWS S3

Maybe a really daft question and please dont mark me down for this, but I finally got Heroku to compile its static assets in my S3 bucket with asset_sync.
Now how do i know that the assets are in fact being served from there, I take it theres no magic going on that pulls them in from s3? I have to set the path for each asset prefixed with
https://s3-eu-west-1.amazonaws.com/pathto/asset
Is there a way to set this in sinatra explicitly, I don't have to manually change every asset do I? that would be silly.
The asset_sync docs say to use this in rails
config.action_controller.asset_host = "//#{ENV['FOG_DIRECTORY']}.s3.amazonaws.com"
but im not sure how to set this in sinatra
EDIT
require 'bundler/setup'
Bundler.require(:default)
require 'active_support/core_ext'
require './config/env' if File.exists?('config/env.rb')
require './config/config'
require "rubygems"
require 'sinatra'
configure :development do
AssetSync.config.action_controller.asset_host = "//#{ENV['FOG_DIRECTORY']}.s3.amazonaws.com"
end
get '/' do
erb :index
end
get '/about' do
erb :about
end
This gives the following error in the console
undefined method `action_controller' for #<AssetSync::Config:0x24d1238> (NoMethodError)
Try putting it in Sinatra's configure block via the Async Built-in initializer, e.g:
configure :production do
AssetSync.config.action_controller.asset_host = "//#{ENV['FOG_DIRECTORY']}.s3.amazonaws.com"
end
It's possible you'll have to call AssetSync.sync as well at some point, I'm not sure.
Edit: using a configure block.
If you were using a modular app (if not, it's not any different, just remove the class bits)
class App < Sinatra::Base
configure :development do
set :this, "and that"
enable :something
set :this_only, "gets run in development mode"
end
configure :production do
set :this, "to something else"
disable :something
set :this_only, "gets run in production"
# put your AssetSync stuff in here
end
get "/" do
# …
end
get "/assets" do
# …
end
post "/more-routes" do
# …
end
# etc
end
See the link I added above for more.
action_controller is part of Rails. To prefix the path, the best thing you could do is use a helper:
helpers do
def aws_asset( path )
File.join settings.asset_host, path
end
end
configure :production do
set :asset_host, "//#{ENV['FOG_DIRECTORY']}.s3.amazonaws.com"
end
configure :development do
set :asset_host, "/" # this should serve it from the `public_folder`, add subdirs if you need to.
end
Then in a route or a view you can do something like this:
aws_asset "sprite_number_1.jpg"
To use with ERB and sinatra-static-assets's image_tag:
image_tag( aws_asset "sprite_number_1.jpg" )
or combine them (this may not work as the image_tag helper might not be seen in the scope of the helper, it's easier to try it than to think about it):
helpers do
def aws_image( path )
image_tag( File.join settings.asset_host, path )
end
end
# in your view
aws_image( "sprite_number_1.jpg" )
I'm sure there'll be an easier way to do this but this will do for a quick and dirty solution.

Using a rackup file to custom configure an application instance

tl;dr How can I get a single Sinatra app to start up very differently on different servers via customizations to config.ru?
Background
I have a single web application written using Sinatra that's run on different servers. Currently the codebase for these servers is forked because there are some non-trivial differences in the way (discrete) parts of them work. For example:
one server authenticates users via an intranet LDAP server, while another server uses a simpler local database table lookup.
one server uses an external cron job to periodically update some statistics, while another (Windows-based) server uses an internal sleepy Thread.
one server stores certain metadata in a local table, while another server pulls the metadata from an external Wiki via screen scraping (!).
…and so on.
I'd like to get these code bases completely shared (single Git repo). I envision that each server would have one slightly-differing configuration file that causes the app to be started up differently.
Abandoned Solutions
I could change the behavior of the app based on environment variables. As there are a not-tiny number of variations in behavior, I'd rather not hide the settings in environment variables.
I could create my own "server-settings.rb" file that is unique to each machine, require it in my app.rb, and then change the configuration there. However, this seems to possibly be re-inventing the wheel. I already have a file named config.ru for each server. Shouldn't I be using this?
The Current Code
My config.ru for the app currently is simply:
require ::File.join( ::File.dirname(__FILE__), 'app' )
run MyApp.new
And the app.rb that it requires is, in essence:
require 'sinatra'
require_relative 'helpers/login' # customized for LDAP lookup on this server
class MyApp < Sinatra::Application
use Rack::Session::Cookie, key:'foo.bar', path:'/', secret:'ohnoes'
set :protection, except: [:path_traversal, :session_hijacking]
configure :production do
# run various code that depends on server settings, e.g.
Snapshotter.start # there is no cron on this machine, so we do it ourselves
end
configure :development do
# run various code that depends on server settings
end
end
The Question
I'd like to make config.ru live up to its name, and have it look something like this:
require ::File.join( ::File.dirname(__FILE__), 'app' )
run MyApp.new( auth: :ldap, snapshot:false, metadata: :remote_wiki, … )
How can I modify my application to change its configuration behavior based on settings supplied via config.ru? Or is this an abuse of config.ru, trying to use it for totally the wrong thing?
As soon as I started reading the question the first answer to pop into my head was "environment variable" but you scotched that straight away :)
I'll go with a mixture of one of your coulds and the desired outcome code, as it's how I structure things…
Because I want to be able to test my applications more easily, I take most of the Ruby out of the config.ru and into a separate config.rb file and leave config.ru to be a bootstrap file. So my standard skel is:
config.ru
# encoding: UTF-8
require 'rubygems'
require 'bundler'
Bundler.setup
root = File.expand_path File.dirname(__FILE__)
require File.join( root , "./app/config.rb" )
# everything was moved into a separate module/file to make it easier to set up tests
map "/" do
run APP_NAME.app
end
app/config.rb
# encoding: utf-8
require_relative File.expand_path(File.join File.dirname(__FILE__), "../lib/ext/warn.rb")
require_relative "./init.rb" # config
require_relative "./main.rb" # routes and helpers
require 'encrypted_cookie'
# standard cookie settings
COOKIE_SETTINGS = {
:key => 'usr',
:path => "/",
:expire_after => 86400, # In seconds, 1 day
:secret => ENV["LLAVE"],
:httponly => true
}
module APP_NAME # overall name of the app
require 'rack/ssl' # force SSL
require 'rack/csrf'
if ENV["RACK_ENV"] == "development"
require 'pry'
require 'pry-nav'
end
# from http://devcenter.heroku.com/articles/ruby#logging
$stdout.sync = true
ONE_MONTH = 60 * 60 * 24 * 30
def self.app
Rack::Builder.app do
cookie_settings = COOKIE_SETTINGS
# more security if in production
cookie_settings.merge!( :secure => true ) if ENV["RACK_ENV"] == "production"
# AES encryption of cookies
use Rack::Session::EncryptedCookie, cookie_settings
if ENV["RACK_ENV"] == "production"
use Rack::SSL, :hsts => {:expires => ONE_MONTH}
end
# to stop XSS
use Rack::Csrf, :raise => true unless ENV["RACK_ENV"] == "test"
run App # the main Sinatra app
end
end # self.app
end # APP_NAME
The initial reason I did this was making it easy to run the app in specs:
shared_context "All routes" do
include Rack::Test::Methods
let(:app){ APP_NAME.app }
end
but it makes sense to me to keep this code with the rest of the application code, so to speak, as I can bundle things together, run other apps etc. I've used this to conditionally load different examples into the specs in a few projects (it helps cut down on duplicated effort and check the examples really work), so I don't see why you couldn't use it to conditionally load configurations.
This way you get to choose to use a conditional in the config.ru as to which config.rb file you would use, or use an env var in the config.rb as to which definiton of self.app to use , or pass in an options hash to self.app…
With your set up I'd rename the APP_NAME module to MyApp, and the Sinatra class to App (because quite often I'll have an website that runs a front end and an API, so the Sinatra classes get named by their function (App, API etc) and wrapped in a module named after the site) and end up with:
config.ru
map "/" do
run MyApp.app( auth: :ldap, snapshot:false, metadata: :remote_wiki )
end
config.rb
def self.app( opts={} )
opts = DEFAULT_OPTIONS.merge opts
# …
run App
end
It'll be interesting to see how other people tackle this.

Broken precompiled assets in Rails 3.1 when deploying to a sub-URI

I'm in the process of updating a Rails 3 app to use Rails 3.1 and as part of that, am making use of the new asset pipeline. So far, I've got everything working apart from one rather annoying problem I can't solve.
The application and all its assets works fine in development, but in production it is deployed to a sub-URI using Passenger (http://the-host/sub-uri/). The problem with this is that the assets are pre-compiled during deployment and one of my CSS (well, it's a .css.scss file) files is making use of the image-url helper from the sass-rails gem. Since during the pre-compilation process, the paths are hard-coded into the precompiled CSS file, the sub-uri is not taken account of:
In my .css.scss file:
body { background-image: image-url("bg.png"); }
The result in the compiled application-<md5-hash-here>.css file:
body { background-image: url(/assets/bg.png); }
What it should be to make it work correctly:
body { background-image: url(/sub-uri/assets/bg.png); }
Is this scenario just asking too much? If so, I'll have to switch back to the old non-asset-pipelined way and just serve my images and CSS from public. However it seems like something which should have been thought about and solved...? Am I missing the solution?
Edit 1: I should note that using the erb solution instead yields the same result, as one would expect.
Edit 2: in response to Benoit Garret's comment
No, the problem isn't related to the config.assets.prefix. I tried setting that (to /sub-uri/assets rather than the default of /assets) but it turned out that was the wrong thing to do - it seems like this setting is already in relation to the root of the Rails app, not the server. Removing that (and thus returning to the default) has fixed all the weird issues that caused (and there were many, all the assets ended up in /sub-uri/sub-uri/assets - it was all very strange). The only problem is that the image-url helper and friends do not pick up the sub-URI when they are pre-compiled. Needless to say, this is logical since when it is pre-compiled, it couldn't possibly know that when it's running under Passenger, it'll be configured in this way. My question is how to inform it of this and thus end up with the correct paths in the precompiled result. If indeed it can be done.
My current workaround is to reference the iamge in the CSS like this: url(../images/bg.png) and place it in the non-pipelined public/images location. Hardly ideal since it doesn't benefit from the fingerprinting and everything which the pipeline provides.
Finally I've worked out a couple of workarounds/solutions.
1) From https://github.com/rails/sass-rails/issues/17 it looks like this could get fixed in sass-rails. I've monkey-patched helpers.rb myself along the lines of the proposed patch in the link above. I simply set the required environment variable in the asset precompile line in deploy.rb.
I do all my monkey patching in a single file config/initializers/gem_patches.rb. In this file I patched this method as:
module Sass
module Rails
module Helpers
protected
def public_path(asset, kind)
path = options[:custom][:resolver].public_path(asset, kind.pluralize)
path = ENV['PRODUCTION_URI'] + path if ENV['PRODUCTION_URI']
path
end
end
end
end
2) Alternatively if you are ok to embed images in the CSS, changing the stylesheet to have a .erb extension, and replacing the image-url("bg.png") with url(<%= asset_data_uri "bg.png" %>) will work without any need to change sass-rails. asset-data-uri doesn't exist as a pure Sass function so you have to use the Rails helper asset_data_uri.
In the latest Rails 3.1.3 you need to monkey patch a different module now, for it to work
This is what I did
module Sprockets
module Helpers
module RailsHelper
def asset_path(source, options = {})
source = source.logical_path if source.respond_to?(:logical_path)
path = asset_paths.compute_public_path(source, asset_prefix, options.merge(:body => true))
path = options[:body] ? "#{path}?body=1" : path
if !asset_paths.send(:has_request?)
path = ENV['RAILS_RELATIVE_URL_ROOT'] + path if ENV['RAILS_RELATIVE_URL_ROOT']
end
path
end
end
end
end
And in my deploy.rb I have:
desc "precompile the assets"
namespace :assets do
task :precompile_assets do
run "cd #{release_path} && rm -rf public/assets/* && RAILS_ENV=production bundle exec rake assets:precompile RAILS_RELATIVE_URL_ROOT='/my_sub_uri'"
end
end
before "deploy:symlink", "assets:precompile_assets"
I'm using Rails 3.1.3 and deploying to a sub-URI successfully.
I have NOT monkey-patched anything.
The key problems with this setup have been better discussed here. As you can see, the solution was applied to Rails 3.2 and never backPorted to 3.1.4.
But, I have came to a solution using Rails 3.1.3 that works for my setup.
Try this: (I'm no expert, just trying to contribute to solve a problem that hassled me for hours...)
environment.rb:
#at top:
ENV['RAILS_RELATIVE_URL_ROOT'] = '/rais'
production.rb:
config.assets.prefix = ENV['RAILS_RELATIVE_URL_ROOT'] ? ENV['RAILS_RELATIVE_URL_ROOT'] + '/assets' : '/assets'
routes.rb:
Rais::Application.routes.draw do
scope ENV['RAILS_RELATIVE_URL_ROOT'] || '/' do #see config/environment.rb
<<resources here>>
end
end
As you can see, I've put assets.prefix inside production.rb, not in application.rb
After that you do:
rake assets:clear
rake assets:precompile
and than, test with the console:
RAILS_ENV=production rails console
Results:
foo = ActionView::Base.new
foo.stylesheet_link_tag 'application'
=> "<link href=\"/rais/assets/layout.css?body=1\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/rais/assets/application.css?body=1\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />"
foo.image_tag('arrow-up.png')
=> "<img alt=\"Arrow-up\" src=\"/rais/assets/arrow-up-ca314ad9b991768ad2b9dcbeeb8760de.png\" />"
After a bit of digging around, I have found the issue. The issue is in Rails, specifically Sprockets::Helpers::RailsHelper::AssetPaths#compute_public_path. Sprockets::Helpers::RailsHelper::AssetPaths inherits from ActionView::AssetPaths and overrides a number of methods. When compute_public_path is called through the Sass::Rails::Resolver#public_path method is sass-rails, the rails sprocket helper picks up the task of resolving the asset. Sprockets::Helpers::RailsHelper::AssetPaths#compute_public_path defers to super which is ActionView::AssetPaths#compute_public_path. In this method there is a condition of has_request? on rewrite_relative_url_root as seen below:
def compute_public_path(source, dir, ext = nil, include_host = true, protocol = nil)
...
source = rewrite_relative_url_root(source, relative_url_root) if has_request?
...
end
def relative_url_root
config = controller.config if controller.respond_to?(:config)
config ||= config.action_controller if config.action_controller.present?
config ||= config
config.relative_url_root
end
If you look at the internals of rewrite_relative_url_root it relies on a request to be present and the ability to derive it from the controller variable in order to resolve the relative url root. The issue is that when sprockets resolves these assets for sass it does not have a controller present and therefore no request.
The solution above didn't work in development mode for me. Here is the solution that I am using to make it work for now:
module Sass
module Rails
module Helpers
protected
def public_path(asset, kind)
resolver = options[:custom][:resolver]
asset_paths = resolver.context.asset_paths
path = resolver.public_path(asset, kind.pluralize)
if !asset_paths.send(:has_request?) && ENV['RAILS_RELATIVE_URL_ROOT']
path = ENV['RAILS_RELATIVE_URL_ROOT'] + path
end
path
end
end
end
end

Why do I need to run my sinatra app again when making changes and my environment is not :development?

I just implemented Compass configuration for my Sinatra app but when I change the environment to :test or :production and modify my files like screen.sass or index.haml my changes are not reflected when I reload the page so I need to run my app again?
Is it normal? Is is just me?
This is how my app.rb file looks like:
require 'sinatra'
require 'haml'
require 'sass'
require 'compass'
require './helpers.rb'
configure do
set :environment, :test
Compass.configuration do |config|
settings.environment == :production ?
config.output_style = :compressed :
config.output_style = :nested
settings.environment == :development ?
config.line_comments = true :
config.line_comments = false
end
set :sass, Compass.sass_engine_options
end
before do
#js = 'javascript:;'
end
get '/scripts/jquery.js' do
# Downloads the latest jQuery 1.x version when needed. Requires to reload the page after done.
`curl "https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" >> public/scripts/jquery.js`
end
get '/styles/:name.css' do
sass :"styles/#{params[:name]}"
end
get '/?' do
haml :index
end
get '/:page/?' do
haml params[:page].to_sym
end
Any idea?
Generally, if you make a change to a running Sinatra application, you have to restart the application, as the program has already been loaded to memory.
There are options for automatically detecting changes and restarting the application on the Sinatra FAQ.
Since Shotgun fix the issue partially (reloading the files for your at production, maybe try with Sinatra::Reloader which, IMHO, works better than Shotgun.
Maybe something like (not tested)
require "sinatra"
configure(:production) do |c|
require "sinatra/reloader"
c.also_reload "*.sass", "*.haml"
end
That being said, are you sure you do need this kind of behavior on a production/test environment for updating? Development env. should be (at least, for what I use it for) for this kind of hot testing.
I used to use sinatra::reloader
but I didn't like the huge dependencies incurred (as should we all be mindful how many gems get activated)
pistol ( at a tender age of ver 0.0.2) and I think does the required job nicely
I use shotgum gem for this.
gem install shotgun
then
shotgun app.rb
from within the app dir
this then reloads the app per request, rather than holding the whole thing in memory. you access the site on localhost:9393

Resources