Access APP_CONFIG['var'] inside routes? or supply routes with variables? - ruby-on-rails

I need to do in rails 4 supply some ip address to set a constraint on certain routes.
Is there a way to get this data from a config file without harcoding it into the routes file?
Im using a yaml file and initializer for app variables like:
APP_CONFIG = YAML.load_file("#{Rails.root}/config/application.yml")[Rails.env]
so normally I could do:
constraints(:ip => %w[APP_CONFIG['app_url']]) do
.. my routes..
end
This fails in the routes.rb is there a way to fix this?

The routes.rb file is a ruby file which is instantiated once, and loaded into memory.
You can just add the ruby code inside it and it will be executed once:
Rails.application.routes.draw do
app_config = YAML.load_file("#{Rails.root}/config/application.yml")[Rails.env]
constraints(:ip => %w[app_config['app_url']]) do
.. my routes..
end
end
This will instantiate the routes.rb file with the variable loaded from the yml and available throughout your rails routes app. You don't even need to use a env variable. Local variable seems a better idea.
You can also put logic inside and make it environment dependant:
if Rails.env.production?
app_config = YAML.load_file("#{Rails.root}/config/application.yml")[Rails.env]
constraints(:ip => %w[app_config['app_url']]) do
.. my routes..
end
else
.. my routes ...
end

Taking a look at the initialization process of rails (http://guides.rubyonrails.org/initialization.html). You'll see that routing is actually loaded quite early (and earlier than application.rb or other initializers). It has therefore not yet loaded this file.
A way round this would be to place this into your boot.rb:
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
#Now load app config:
require 'yaml'
APP_CONFIG = YAML.load_file(File.expand_path('../../config/application.yml', __FILE__))

I believe you are running into a load order issue. You could probably hack around this, but...
I would highly recommend using Figaro to solve this problem. It is a gem specifically designed for rails configuration and will work nicely with 12 factor app deployments (like Heroku): https://github.com/laserlemon/figaro
I am using Figaro in the app I am currently working on and was able to confirm access to the env variables within my routes file. I believe this gem will solve your current issue and other config issues you don't even know you have yet!

Related

Accessing Rails engine's URL helpers in initializer

I'm trying to access the url helpers in my engine to set up rack-cors. Right now, I've hard-coded the strings for one of the urls in the rack-cors middleware configuration. I have read the order in which Rails initializers are run, and at this point in the load order I should have the engine routes available to me. I thought I would have them available at the event add_routing_paths, but I couldn't find the routes after digging around using pry. Another statement that leads me to think I'm doing this incorrectly is that the docs say: "Some parts of your application, notably routing, are not yet set up at the point where the after_initialize block is called." According to this list:
require "config/boot.rb" to setup load paths
require railties and engines
Define Rails.application as "class MyApp::Application < Rails::Application"
Run config.before_configuration callbacks
Load config/environments/ENV.rb
Run config.before_initialize callbacks
Run Railtie#initializer defined by railties, engines and application.
One by one, each engine sets up its load paths, routes and runs its config/initializers/* files.
Custom Railtie#initializers added by railties, engines and applications are executed
Build the middleware stack and run to_prepare callbacks
Run config.before_eager_load and eager_load! if eager_load is true
Run config.after_initialize callbacks
I'm trying to hook into (7), but perhaps routes aren't available until (11)?
module Zillow
class Engine < ::Rails::Engine
isolate_namespace Zillow
# Rails.application.routes.url_helpers
initializer "zillow.cors", after: :set_routes_reloader do |app|
require 'pry'; binding.pry
app.config.app_middleware.insert_before 0, Rack::Cors do
allow do
origins 'localhost:3000'
resource '/zillow/search_results', methods: :get
end
end
end
end
end
Here is the output of my routes
zillow /zillow Zillow::Engine
Routes for Zillow::Engine:
api_deep_comps GET /api/deep_comps(.:format) zillow/api#deep_comps
api_zestimate GET /api/zestimate(.:format) zillow/api#zestimate
api_search_results GET /api/search_results(.:format) zillow/api#search_results
api_updated_property_details GET /api/updated_property_details(.:format) zillow/api#updated_property_details
You can fire your own event when routes are loaded and then subscribe to that event during initialization to get routes data. For that:
Add this to the end of config/routes.rb file (outside of routes.draw block)
ActiveSupport::Notifications.instrument 'routes_loaded.application'
Subscribe to this event in initialization code and use URL helpers!
ActiveSupport::Notifications.subscribe 'routes_loaded.application' do
Rails.logger.info Rails.application.routes.url_helpers.home_path
end
For more information see:
Subscribing to an event
Creating custom events
Load it just after initialize.
I needed to use root_url in my initializer but it wasn't properly initialized. I tried using the Pub / Sub method that Anton Styagun suggests but even when that's called the URL Helpers were not properly initialized.
Instead, I opted to load my initializer file manually just after the Rails app itself has initialized. This takes a few steps:
Move your initializer from config/initializers/ to lib/.
Require your file in environment.rb just after your application initializes. Ours looks something like this:
CNTRAL::Application.initialize!
require "my_initializer" # Omit the `lib/` because that's the load path it looks in.
Now, inside of my_initializer.rb I was able to call root_url by doing this:
Rails.application.routes.url_helpers.root_url
NOTE: It's important not to just use include Rails.application.routes.url_helpers. It raised an exception on me.
I also added a comment in both the initializer and the environment.rb of where this was loaded/required so it's more obvious to myself in the future and other engineers.
Alternative Method
You can also use the following method and it works well:
In your application.rb file, add:
config.after_initialize do
Rails.application.reload_routes! # Necessary to load the Routes.
require "my_initializer"
end
After looking into this further and reading the rack-cors example done in Rails3, it may not be possible to retrieve the route helpers inside the initializer at any point in time.

Auto include in Rails Console

I find myself having to type (for example)
include PathHelper
every time I load the Rails Console.
Is there a way to configure the Rails console to automatically include certain modules?
The syntax for configuring rails console has changed. I found this on RailsGuides:
http://guides.rubyonrails.org/configuring.html#rails-general-configuration
console do
# this block is called only when running console,
# so we can safely require pry here
require "pry"
config.console = Pry
end
Just in case anyone still feel confused, the simplest way to do this is:
go to the root directory of your project
create an .irbrc file(if you use rails console) or .pryrc file(if you use pry)
put whatever you need to include in it
For example, if you use the default rails console and need to include PathHelper, just put it in the file:
# RootDirectoryOfYourProject/.irbrc
include PathHelper
The PathHelper will be included automatically when you do rails console
If you are still looking for an answer, this is what I do,
I created a file ~/.irbrc in which you put all the code you want to be auto loaded in your rails console.
This is the content of my file:
require "awesome_print"
include Rails.application.routes.url_helpers
AwesomePrint.irb!
def y(obj)
puts obj.to_yaml
end
I would check out this question.
Basically, modify your config/application.rb file to include the paths to any modules you want to auto-load.

Rails mountable engine: how should apps set configuration variables?

I have a mountable engine called Blog that apps can use.
What's the best way to allow apps that use the engine to set a configuration variable like site_name (so that the engine can display it in the design)?
Update:
I've seen some gems create a 'config/initializers/gem_name.rb' file. Is there any specification on how to:
Create a file like that on the engine's side
Copy it into the app's side
How to access those set variables on the engine's side?
I tried creating Blog.site_name = "My Site" in the app's config/initializers/blog.rb file but get an Undefined method error.
Figured out an even better solution that also allows you to set default values (incase the app using the engine doesn't specify a config)...
Create config variables in your app's /config/initializers/blog.rb like this:
Blog.setup do |config|
config.site_name = "My Site Name"
end
In your engine's /lib/blog/engine.rb set default values like this:
module Blog
self.mattr_accessor :site_name
self.site_name = "Site Name"
# add default values of more config vars here
# this function maps the vars from your app into your engine
def self.setup(&block)
yield self
end
end
Now you can simply access the config variables in your engine like this:
Blog.site_name
Much cleaner.
After a lot of testing and looking into existing gems, here is what works in Rails 4:
Considering your engine's name is Blog:
In your engine's /lib/blog/engine.rb put in this:
module Blog
def self.setup(&block)
#config ||= Blog::Engine::Configuration.new
yield #config if block
#config
end
def self.config
Rails.application.config
end
end
In your app, create a file called /config/initalizers/blog.rb and set config vars like this:
Blog.setup do |config|
config.testingvar = "asdfasdf"
end
Then you can access these config variables ANYWHERE in your engine like this:
Blog.config.testingvar
Hope this helps someone. There is very little documentation on this right now so it was all trial and error.
I know this is a fairly old post, but in the event someone in the future finds this, I'd like to recommend the Angostura gem for passing dependencies into a Rails engine. To use it, assuming my engine is called 'Blog' and I want to access a variable called 'site_name', the engine's lib/blog.rb looks something like:
require "blog/engine"
require "angostura"
module Blog
include Angostura::Dependencies
dependency :site_name
end
In my main app, in config/initializers/blog.rb, I added
Blog.setup do |config|
config.site_name = "My site name"
end
Now, I can access site_name in my engine by calling Blog.site_name.
I'd like to point out that defaults are also supported, so you could do something like dependency site_name: 'Default site name' in lib/blog.rb. Furthermore, you can pass in whole classes as dependencies by sending it stringified classnames, like config.my_class = 'MyClass'.
For posterity, in order to use the gem, I added s.add_dependency "angostura", "0.6.0" in my engine's gemspec, and then ran bundle install.

Where do you put your Rack middleware files and requires?

I'm in the process of refactoring some logic built into a Rails application into middleware, and one annoyance I've run into is a seeming lack of convention for where to put them.
Currently I've settled on app/middleware but I could just as easily move it to vendor/middleware or maybe vendor/plugins/middleware...
The biggest problem is having to require the individual files at the top of config/environment.rb
require "app/middleware/system_message"
require "app/middleware/rack_backstage"
or else I get uninitialized constant errors on the config.middleware.use lines. That could get messy very quickly. I'd rather this was tucked away in an initializer somewhere.
Is there a conventional place to put this stuff?
The specific answer I'm looking for with this bounty is: where can I put the require lines so that they are not cluttering the environment.rb file but still get loaded before the config.middleware.use calls? Everything I have tried leads to uninitialized constant errors.
Update: Now that we're using Rails 3.0, I treat a Rails app like any other Rack app; code files for middleware go in lib (or a gem listed in Gemfile) and are required and loaded in config.ru.
As of Rails 3.2, Rack middleware belongs in the app/middleware directory.
It works "out-of-the-box" without any explicit require statements.
Quick example:
I'm using a middleware class called CanonicalHost which is implemented in app/middleware/canonical_host.rb. I've added the following line to production.rb (note that the middleware class is explicitly given, rather than as a quoted string, which works for any environment-specific config files):
config.middleware.use CanonicalHost, "example.com"
If you're adding middleware to application.rb, you'll need to include quotes, as per #mltsy's comment.
config.middleware.use "CanonicalHost", "example.com"
You can put it in lib/tableized/file_name.rb. As long as the class you're trying to load is discoverable by its filename, Rails will automatically load the file necessary. So, for example:
config.middleware.use "MyApp::TotallyAwesomeMiddleware"
You would keep in:
lib/my_app/totally_awesome_middleware.rb
Rails catches const_missing and attemts to load files corresponding to the missing constants automatically. Just make sure your names match and you're gravy. Rails even provides nifty helpers that'll help you identify the path for a file easily:
>> ChrisHeald::StdLib.to_s.tableize.singularize
=> "chris_heald/std_lib"
So my stdlib lives in lib/chris_heald/std_lib.rb, and is autoloaded when I reference it in code.
In my Rails 3.2 app, I was able to get my middleware TrafficCop loading by putting it at app/middleware/traffic_cop.rb, just as #MikeJarema described. I then added this line to my config/application.rb, as instructed:
config.middleware.use TrafficCop
However, upon application start, I kept getting this error:
uninitialized constant MyApp::Application::TrafficCop
Explicitly specifying the root namespace didn't help either:
config.middleware.use ::TrafficCop
# uninitialized constant TrafficCop
For some reason (which I've yet to discover), at this point in the Rails lifecycle, app/middleware wasn't included in the load paths. If I removed the config.middleware.use line, and ran the console, I could access the TrafficCop constant without any issue. But it couldn't find it in app/middleware at config time.
I fixed this by enclosing the middleware class name in quotes, like so:
config.middleware.use "TrafficCop"
This way, I would avoid the uninitialized constant error, since Rails isn't trying to find the TrafficCop class just yet. But, when it starts to build the middleware stack, it will constantize the string. By this time, app/middleware is in the load paths, and so the class will load correctly.
For Rails 3:
#config/application.rb
require 'lib/rack/my_adapter.rb'
module MyApp
class Application < Rails::Application
config.middleware.use Rack::MyAdapter
end
end
I'm not aware of a convention, but why not put it in the /lib directory? Files in there get automatically loaded by Rails.
You could create an initializer which requires the necessary files and then leave the files wherever you want.
According to this the initializers are executed before the rack middleware is loaded.
The working solution I have so far is moving the middleware requires to config/middleware.rb and requiring that file in environment.rb, reducing it to a single require which I can live with.
I'd still like to hear how other people have solved this seemingly basic problem of adding middleware to Rails.

How to gemify a Rails (engine) plugin?

I have a engine style Rails plugin from which I can create a gem using Jeweler. But when I require it in my Rails environment (or erb) the models within the plugin are not loaded. I have followed a number of tutorials and read just about everything on the subject.
# environment.rb
config.gem 'myengine'
# in irb
require 'myengine'
I have unpacked the gem and verified that all files are present. My init.rb has been moved to a new folder called 'rails' as per. All files in 'lib' are automatically added to the $LOAD_PATH, so require 'myengine' runs lib/myengine.rb. I verified this by putting a puts 'hello' within.
Is it because of the physical presence of plugins in a known place that Rails can add all the models, controller etc. to the relevant load_paths? Do I need to replicate this manually when using a gem?
Would gemspec require_paths be a way of adding additional paths other than lib? I assume however that Rails does not just require every single file, but loads them on demand hence the need for the filename and class name to match?
%w{ models controllers helpers }.each do |dir|
path = File.join(File.dirname(__FILE__), 'app', dir) + '/'
$LOAD_PATH << path
puts 'requiring'
Dir.new(path).entries.each do |file|
if file =~ /\.rb/
puts file
require file
end
end
end
By adding the above to lib/myengine.rb all the models/controllers are required. But like I said in my question this is unlikely to be a good way forward.
Offhand I'd say the part about adding those directories to the search path is right on. What you shouldn't need to do is require each file manually (as you allude to in your last sentence). What Rails does when you reference a non-existent constant is to search for a file with the same name (underscored of course) in the load path.
If for some reason you can not abide by the constraint (think about it long and hard) then you are going to need to dig deeper into Rails and see how the reloading mechanism works so you can tie into it properly in development mode.
The problem was the files (in app) where not being added to the gem because when using Jeweler it only automatically adds files to required_paths which are committed to git.

Resources