How to serve static files in Rails from a mounted path - ruby-on-rails

I am trying to develop a gem to be used in Rails applications that includes serving static files (html, js, and css) at a mounted location. Using the gem's UI will be accomplished by adding the following to config/routes.rb
mount MyGem::UI.new, at: "/foo"
I have this almost working using Rack::Static, but am not attached to this solution if there is a better one.
module MyGem
module_function
def root
#root ||= Pathname(__dir__).expand_path
end
end
module MyGem
class UI
def call(env)
static_app.call(env)
end
def static_app
Rack::Static.new(nil, static_options)
end
def static_options
{urls: [""], root: asset_path, index: 'index.html'}
end
def asset_path
#root ||= MyGem.root.join("assets")
end
end
end
The files at MyGem.root/assets can be reduces to a single html file that links to a single css file in the same location using href="./index.css". (In reality they will be a React app built with homepage: "./" in package.json using create-react-app to produce ./ relative links to all assets.) I have tested this setup using both the dummy and the full-fledge React app.
This setup works if and only if I go to localhost:3000/foo/ with the trailing slash. However, if I navigate to localhost:3000/foo without the trailing slash, I successfully get the index.html page, but all of its links are broken, returning 404 not found:
GET http://localhost:3000/index.css net::ERR_ABORTED 404 (Not Found)
How can I avoid this and robustly server my static content at the mounted location, "/foo"? It's halfway acceptable as a pilot to just require the trailing slash, but is a bit dodgy to leave in a published gem.
I am open to solutions involving alternate rails routes configurations, different ways to serve the static content (contingent on being easily describable to gem users), and different ways to build the static content, and anything else.

Related

Really static pages with Rails

Some page in the application must be accessible even application is down. For example pages for 50x errors. The easiest way to do so - create static HTML pages, which will be served by web-server (like apache on Nginx). Most of this pages have a common layout with the application. So, if we change some part of layout in the application we must change all static pages by hand.
What is the best way to store rails pages as static files and recreate it (automatically or by rake task) on same changes in the project? Is any gem for rails or static-site generator that's able to reuse rails layout and resources (CSS, js, images).
Generally static content goes in your public folder which you can configure Nginx or equivalent to route to accordingly without even needing to hit Rails.
For static site generation in Ruby you might want to check out Jekyll https://jekyllrb.com/. You could manage your Jekyll site separately from your Rails site and generate the static HTML/CSS/JS on deployment. There's a jekyll watch command that will listen for file edits and compile your static content accordingly.
There simple rake task (via GIST) to load all files from VIEW_PATH, wrap it with application layout and store it under same path in public. Work with Rails 4.
https://gist.github.com/potapuff/090b2da4a4156c1272430241cb70edc0
namespace :static do
desc 'Render all resources'
task :publicate => :environment do
resources(VIEW_PATH).each do |src, dest|
html= controller.render_to_string(file:src, layout:'application')
dirname = File.dirname(dest)
unless File.directory?(dirname)
FileUtils.mkdir_p(dirname)
end
File.write(dest, html)
end
end
def resources search_path
...
end
def controller
ApplicationController.new.tap do |controller|
...
end
end
end
Other possibility is using gem render_anywhere .
In Rails 5 we have new ability to use render outside controllers
https://medium.com/evil-martians/the-rails-5-post-9c76dbac8fc#1b36

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

Rails 4 reloading code in lib folder

I know, this question was asked at least milion times, but I still can't find the right solution. I mean clean and easy, like it is supposed to be in Rails applications.
Let's imagine I am developer of an application that needs some code in the lib folder. My app's name is Xy. I will create a file xy.rb in the lib folder with this content:
require 'xy/version'
module Xy
end
After that I will create next file in lib/xy/ named version.rb:
module Xy
module VERSION
def self.to_s
"1.0.0"
end
end
end
So far, so good. Then I will create some controller and I will edit routes:
class IndexController < ApplicationController
def index
render text: Xy::VERSION
end
end
root 'index#index'
I also need require my code, in application.rb I will add require 'xy'
Then I will run my server rails s and I will wisit the app in the browser http://localhost:3000 - seems good, I see 1.0.0.
Then I will update the version string in Xy::VERSION to 1.0.1 and I'll refresh the browser - here it comes, I will no see the change. To see it I will have to restart the server. And this is quite anoying.
So the question is: What is the right way to force RoR 4+ to reload changes in the lib folder in development environment?
Thank you in advance.
Add this to application.rb:
config.autoload_paths += %W(#{config.root}/lib)
I'm using this in my app now, you never need to even require' the lib files anywhere and don't have to restart the server. Just make sure you name your files correctly. E.g
/lib/foo/rb
class Foo
/lib/foo/bar.rb
class Foo::Bar

Rails App to Angular: HAML + Rails Helpers

I'm trying to move my full-stack rails app over to Angular a page at a time. I'm using ui-router (https://github.com/angular-ui/ui-router) and angular-rails-templates (https://github.com/pitr/angular-rails-templates). I assumed the nghaml extension would allow me to continue to use rails helpers such as link_to, paths, etc. in my haml so just copied and pasted my haml page into the template; in an ideal world I would now be at a point where one page was being served client-side and every other page (including the ones it's linked to) were still being served server-side. Instead, I'm getting errors such as:
undefined local variable or method `dashboard_patients_path' for #<Object:0x007fc87865cff0>
and link_to, etc.
I thought this (angularjs with client side haml) would be a solid solution, specifically sharpper's response since it seemed directly applicable.
module CustomHamlEngine
class HamlTemplate < Tilt::HamlTemplate
def evaluate(scope, locals, &block)
scope.class_eval do
include Rails.application.routes.url_helpers
include Rails.application.routes.mounted_helpers
include ActionView::Helpers
end
super
end
end
end
Rails.application.assets.register_engine '.haml', CustomHamlEngine::HamlTemplate
However, even after restarting the server, no dice.
Thoughts?
Got stuck with the same problem and found a solution after investigating angular-rails-templates, attentively reading their documentation and using the solution proposed by sharpper in angularjs with client side haml.
angular-rails-templates needs to recreate a mimeless version of the haml engine. So they extend the classes that are registered with Tilt instead of using the engines that were added to the asset pipelines. Therefore the new CustomHamlEngine that we create and register with the asset pipeline is never used by angular-rails-template. What we need to do instead is to register the engine with Tilt.
Create a file called angular_rails_templates.rb in the config/initializers folder and put this code in it.
# config/initializers/angular_rails_templates.rb
module CustomHamlEngine
class HamlTemplate < Tilt::HamlTemplate
def evaluate(scope, locals, &block)
scope.class_eval do
include Rails.application.routes.url_helpers
include Rails.application.routes.mounted_helpers
include ActionView::Helpers
end
super
end
end
end
Tilt.register CustomHamlEngine::HamlTemplate, '.haml'
That will override the usual .haml engine with the one we just created. Angular-rails-templates will then process your haml file and it will support the rails helpers as well as the path helpers.
Don't forget to restart the server after including the file.

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