How to override public folder path in Rails 4? - ruby-on-rails

I would like to use a different public folder from a parent directory called client which contains the entire AngularJS app. Essentially I want to tell Rails to load AngularJS app and the only job that Rails has to do is serve JSON.
Is that possible in Ruby on Rails?

As others have mentioned, it may or may not be a great idea to override the existing paths['public'] folder. But you can do the following safely in somewhere like application.rb:
Rails.application.config.middleware.insert_after(
ActionDispatch::Static,
ActionDispatch::Static,
Rails.root.join("client").to_s,
Rails.application.config.static_cache_control
)
The public folder is exposed to the web server through the Rack middleware ActionDispatch::Static. There's nothing else special about it, and the above code simply adds another instance of the middleware that points to the directory client. So in the above case, the browser would be able to access everything in public as well as client.

Just had to do it myself for an Angular app.
Put this in your application.rb:
config.serve_static_files = true
paths['public'] = File.join 'client', 'app'
Or if you still use asset pipeline (config.assets.enabled = true):
paths['public/javascripts'] = File.join 'client', 'app', 'scripts'
paths['public/stylesheets'] = File.join 'client', 'app', 'styles'
Would be interesting to know if there are any consequences with the second bit as my front-end is served completely separately thus I keep asset pipeline switched off and use grunt instead.

You can define another path like
# config/application.rb
paths['my_website'] = 'website'
Then you can use this path in your routes like
# routes.rb
get '/my_website', to: redirect('my_website/index.html')

Related

Rails Sprockets file uses local manifest for digests instead of asset host version

In our web application built in Rails we have several clients using the same application who will have different assets that are used dependant on which subdomain is used.
To achieve this we swap out what folder is being used on the CDN like so:
config.action_controller.asset_host = Proc.new { |source, request|
if request.subdomain.present?
"http#{request.ssl? ? 's' : ''}://cdn.domain.com/#{request.subdomain}/"
else
"http#{request.ssl? ? 's' : ''}://#{request.host_with_port}/"
end
}
Each time we create a new client we compile the assets manually using a custom build tool that uses Sprockets to build the assets the same way Rails would and then upload them to our CDN under a folder that matches the subdomain. This then allows us to have different sets of assets based purely on the subdomain.
Now this works fine except that when we update an asset the digest will change for that file but Rails will still try and load the old asset digests because the sprockets-manifest file (which is in /public/assets) e.g. .sprockets-manifest-12345.json is being loaded instead of the one that's on the CDN. Even though the asset host is different it still loads the local one.
Rails it seems doesn't care about other manifest files as the file itself only stores the filename to the fingerprinted version so even when things like the host changes it would normally be able to find the correct asset. It would seem as though Rails has been designed this way deliberately.
However we really need to get Rails to use the manifest file that is on the CDN itself rather than use the one in the public folder local to the application.
After reading the docs, it seems you can change the manifest location. We tried doing it by using the same logic as above for the manifest like so:
config.assets.manifest = Proc.new { |source, request|
if request.subdomain.present?
"http#{request.ssl? ? 's' : ''}://cdn.domain.com/#{request.subdomain}/"
else
"http#{request.ssl? ? 's' : ''}://#{request.host_with_port}/"
end
}
But Rails/Sprockets is still using the local sprockets file... Any ideas why?

Rails file location and access

My root is site/home/ubuntu/workspace/
Is it possible to access file (from browser by HTTP request) located inside workspace/ without configuring routes and controllers?
Does the question on 1) depend on file extension?
you can access path to your file like this:
File.expand_path("somestuff.rb", "~/workspace")
for me this code produces path as follows:
"/home/foodie/workspace/somestuff.rb"
I have this structure:
# /home/username/Workspace/rails_project/app/controllers/application_controller.rb
require "#{Rails.root}/../test.rb
And
# /home/username/Workspace/test.rb
# Some ruby code
As you can see, test.rbfile is outside RubyOnRails Project.
For security reason, you can not access a file outside project rails from URL without defining a route. What you can do, is point a route to controller that, based on file name provided at url, require a file outside rails project.
# /home/username/Workspace/rails_project/config/routes.rb
get '/get_file/:file_name', to: 'files#show'
# /home/username/Workspace/rails_project/app/controllers/files_controller.rb
class FilesController < ApplicationController
def show
document = params[:file_name]
send_data "#{Rails.root}/../#{document}, filename: document
end
end
For more info, see:
http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_data
Without configuring routes and controllers, client can only access files in the public/ directory (it doesn't matter what the extension is). Bare in mind this: when your Rails app is run by webserver, its webroot will be the public directory, consequently to access public/file.ext you request should be webroot/file.ext

How do you tell Rails / the asset pipeline that a js.erb depends on a YAML file?

I have a js.erb file that loads YAML from a config file. The problem is that Rails / the asset pipeline will cache the results and never invalidate that cache, even when I change the YAML file contents. I can restart the rails server and even reboot the machine to no avail. The only workaround I've found so far is doing a "rake assets:clean".
I would like to find a way to tell the asset pipeline that when the YAML file changes, it needs to re-compute my js.erb. Or, alternatively, tell it it can only cache the js.erb for the lifetime of the rails server / ensure somehow that re-generation occurs every time the rails server comes up or is restarted.
Any suggestions would be greatly appreciated.
Add this into a file under config/initializers and it will tell the asset pipeline to re-compute the js.erb file that loads the YAML data whenever one of the backing YAML files changes:
class ConstantsPreprocessor < Sprockets::Processor
CONSTANTS_ASSET = "support/constants"
def evaluate(context, locals)
if (context.logical_path == CONSTANTS_ASSET)
Constants.load_path.each do |dir|
dir.each do |yml|
next unless yml.end_with?".yml"
context.depend_on("#{dir.path}/#{yml}")
end
end
end
data
end
end
Rails.application.assets.register_preprocessor(
'application/javascript',
ConstantsPreprocessor)
If you're using Sprockets 3 (with Rails 5, for example), you can use // depends_on. For example, my-constants.js.erb:
//= depend_on my_constants.yml
angular
.module('services.myConstants', [])
.factory('myConstants', [
function() {
return <%= YAML::load_file(Rails.root.join('config/shared/my_constants.yml')).to_json %>;
}
]);
Just make sure the directory containing my_constants.yml is included in asset paths in application.rb:
config.assets.paths.unshift Rails.root.join('config', 'shared').to_s
I think you have 2 options:
Disable the asset pipeline and let Rails do the compilation on the go (bad for performance)
Create a daemon process, separated from Rails (look for Ruby Daemon) to look for any changes in that specific file and recompile the assets.
3 (extra!). Remove the js-YAML dependency and read the content of the YAML from a AJAX call to the app. The scenario is: the JS make a AJAX call, the controller read the YAML file and return the content of it to the JS file. So no need to recompile or watch for changes in the YAML file.
if you choose the 3, don't read the YAML in the controller, create a utility class to do it and let the controller ask that class to read the file and pass it's content.
You can add your own processor directive that works on files outside of the assets directory. = depend_on only works with asset files (https://github.com/rails/sprockets#depend_on)
In config/initializers/sprockets.rb:
Sprockets::DirectiveProcessor.class_eval do
def process_depend_on_project_file_directive(file)
path = Rails.root.join(file).to_s
if File.exists?(path)
deps = Set.new
deps << #environment.build_file_digest_uri(path)
#dependencies.merge(deps)
end
end
end
Usage:
//= depend_on_project_file "config/setting.yml"
See this comment on github for details: https://github.com/rails/sprockets/issues/500#issuecomment-491043517

Prepend path prefix to all rails routes

I have a setup where nginx serves a rails application inside a specific subfolder
eg. http://myserver/railsapp/ and everything inside gets proxied to rails, if the first subfolder is different, it servers static files from another folder.
I haven't been able to find how to specify this behaviour in rails in an intelligent way. I mean, what I want is to specify an option like Rails.server_prefix = /railsapp so that all the routes get prepended automagically, both on the incoming requests and on the generated links.
You probably want to use the router's scope method with the :path argument:
Rails.application.routes do
scope(:path => '/railsapp') do
# the rest of your routes go here
end
end
See the docs for more info.

Render static files in /doc in Rails

So far I have in config/routes.rb:
match 'doc/:path' => 'doc#show'
And in app/controllers/doc_controller.rb:
class DocController < ApplicationController
layout false
def show
render File.join( RAILS_ROOT, 'doc', params[:path] )
end
end
This works find for index.html and other .html files. But it doesn't serve up .css and .js files. It also doesn't serve nested files and directories such as /doc/metrics/output/index.html
How can I get Rails to serve up all static files in /doc but without simply putting a link to them in /public (so that I can autheticate the user in the controller first)?
I would recommend not serving the files through Rails at all. Serve them through your server (Nginx, Apache). You can use the X-Accel-Redirect and the X-Sendfile headers to tell Nginx and Apache to send the static file instead. The benefit of this approach is that you can still authenticate a user before allowing them access to the file. Here's the Nginx tutorial:
http://ablogaboutcode.com/2010/11/19/serving-static-files-passenger-nginx/
Another option is to setup your routes like this:
match 'doc' => 'doc#show'
And pass your path as a parameter so you don't have to do nested URL matching in your routes, or handle special cases (.css, .js, .html, ...)
/doc?path=/path/to/my/document.css

Resources