There are several ways to include controller-specific assets in Rails:
One option which is not DRY is to add = yield :head in the layout and content_for(:head) { ... } in every top-level view. If an asset is controller-specific, it should be specified only once per controller, not in each view. Of course, this approach is awesome for view-specific assets.
A second option which is not declarative is to add an asset corresponding to the controller name if it exists. Instead of checking whether something exists, I should simply say (where appropriate) that it exists and must be included. Also, I'm not sure if the response would be cached to avoid a runtime performance hit. On the positive side, this approach wouldn't require any changes to views or controllers, but it might open up the possibility for name clashes, especially with legacy models.
A third option is to include all assets of a type in a single file. Browsers shouldn't download assets they don't need, and this would make it more difficult to debug the application. This option would be fine if the total asset size is still manageable.
Is there some way to declaratively include a single controller-specific asset in a separate file in a DRY way without breaking the MVC model using very little code?
Rails will serve only the code in the controller specific asset files to the specified controller if you use the following include commands in your application layout:
<%= javascript_include_tag params[:controller] %>
<%= stylesheet_link_tag params[:controller] %>
I suspect if you do this you will need to also do the following:
Still include the <%= javascript_include_tag :application %> and <%= stylesheet_link_tag :application %> to get all your cross controller assets
Check how the require_tree . directives work to ensure that the controller specific assets are not being loaded both by the application.css and the <%= stylesheet_link_tag params[:controller] %> in fact you may need to remove the require_tree . and load any cross controller sheets directly into the application files
See the Rails Guide on the Asset Pipeline in section 2 for more information.
Related
I am creating a rails landing page for a client and he wants the app to be a subpage of his website.
app.herokuapp.com
website.com/app
How can I configure the domain so that when the user types website.com/app
The app.herokuapp.com appears instead?
Edit.
Obs. The client website is big and runs on WordPress installed on domains.com
I am new to rails and the domain services, sorry if it is too of a basic question.
Heroku must run the rails app in the root of your domain. So basically what you're trying to do is impossible using just Heroku and whatever domain service.
However the desired result can be achieved by making the whole thing a rails app but with namespaces. This can only work if the website is doesn't require a server to run. i.e it's static with just HTML, CSS and JS but with no framework like Angular or CMS like WordPress. Here's an outline of the steps one would need to take to do that:
First of all, to make your "rails app" exist in /app, you can move all the "application related" routes to an "app" namespace.
In your routes.rb:
Rails.application.routes draw do
namespace :app do
# all application routes should be placed in here
end
end
You need to move all your app related controllers to a /controllers/app.
All the application controllers need have their classes renamed to have the proper namespace. So you need to change every class WhateverController < ApplicationController to class App::WhateverController < ApplicationController
You need to move all the application related views to /views/app
This step is a little tricky. And it's only necessary if the assets (css, javascript) differ between the website and application. You need to create app.js, website.js, app.css, website.css that will live in /assets/javascripts and /assets/stylesheets respectively. You also need to create "website" and "app" folders in both /assets/stylesheets and assets/javascripts. Move all the application related assets to the corresponding /javascripts/app and /stylesheets/app folders.
Add the following code:
To app.js:
//= require_tree ./app
To app.css:
*= require_tree ./app
Change your application.html.erb layout's assets links to:
<%= stylesheet_link_tag 'app', media: 'all' %>
<%= javascript_include_tag 'app' %>
The above code tells the layout to look for app.js and app.css and include what they require. And what they require is everything in the `/app/ folders you have in the different assets folders.
If you push/deploy to Heroku, the application should live in website.com/app (provided you've pointed "website.com" properly to Heroku. See Reference).
The next batch of steps is to make the website live within the rails app in the root namespace.
Move all the required website assets (html, css, javascript) to the corresponding /javascripts/website and /stylesheets/websites folders in your assets folder.
Add the following code:
To website.js:
//= require_tree ./website
To app.css:
*= require_tree ./website
Create a new layout (website.html.erb). It should look very similar to application.html.erb except:
<%= stylesheet_link_tag 'website', media: 'all' %>
<%= javascript_include_tag 'website' %>
Now you can create the controller that will manage the website pages (WebsiteController) in /controllers.
class WebsiteController < ApplicationController
layout 'website' #we need to tell it which layout to use
def homepage
end
def about
end
#basically just create methods/actions for every page on the website
end
For every action, create a corresponding view in /views/website. For example the homepage view should be homepage.html.erb. Also copy and paste the HTML from the website into the right .html.erb files.
To tie it all up with routes.
root 'website#hompepage'
get '/about' => 'website#about'
get '/contact' => 'about#contact'
#and so on...
namespace :app do
# all application routes should be placed in here
end
Finally make sure to go through all the markup everywhere to replace all link_to paths and src for img tags. Even the paths for the app would need to be changed because all the routes have been changed.
Another option would be to use the high voltage gem to add the static website html to your rails app.
I created a new Rails app and when I am in some view of a controller called Welcome, if I check the source code I see that the welcome.css is being added, even though I don't specify that in the layout or explicitly anywhere in my code.
However, in another Rails app when I am in a controller, let's call it welcome, whenever I am in the view, I don't see the welcome.css being loaded.
How does Rails decide whether yes or not require the css for a specific controller? From the Rails guides it seemed to me that you have to specificly add the stylesheet_link_tag params[:controller] in order to load the specific css, but this is not the case in my first application, where I don't set that anywhere.
By default in rails 3.2, rails adds a stylesheet_link_tag in the app/views/layouts/application.html.erb
<%= stylesheet_link_tag "application", :media => "all" %>
and requires all your stylesheets in the app/assets/stylesheets/application.css
*= require_self
*= require_tree .
that's the reason why your rails 3.2.2 app adds all your stylesheets automatically. if you don't want to load all your stylesheets, just edit these two files.
In our current rails app, we are following certain patterns for including assets such as scripts and stylesheets.
For instance, one such pattern is (code inside the layout):
= stylesheet_link_tag controller.controller_name
The problem here is that not all of the controllers are going to have associated stylesheets. What is the best way to check if an asset exists? Specifically, I know there is some trickery here due to the cache busting asset names.
Finally figured this one out. Asset existence can be checked as follows:
YourApp::Application.assets.find_asset("#{asset}.css").nil?
The answer would then be:
= stylesheet_link_tag controller.controller_name if YourApp::Application.assets.find_asset("#{controller.controller_name}.css")
I had found this answer before and we'd been happily using find_asset in our application (Rails 3.2.16), until one day it started bombing out.
After some digging, it turns out that even in a production environment, with asset precompilation enabled, the first call to find_asset will attempt to actually compile the asset it's looking for, even if that asset has been precompiled already. As you can imagine, this is not good news -- in our specific case, we were pulling in a Compass library file into a stylesheet, which worked in dev and during precompile, but not in production, where Compass was not in the assets load path.
Long story short, find_asset is not a bulletproof way to determine if an asset is available to include. You can read a bunch more about it in the issue someone tried to file about this, and which was subsequently closed as not a bug: https://github.com/sstephenson/sprockets/issues/411
The real way to determine if an asset exists, and which works in both compile and precompile modes is demonstrated in the hoops that the filer of the above issues needed to jump through. Here's the diff for his fix: https://github.com/fphilipe/premailer-rails/pull/55/files
I'm putting this here in hopes that other Googlers who find this don't fall into the same trap I did!
To include an asset based on controller name
<% controller_asset = controller.controller_name %>
<%= stylesheet_link_tag controller_asset if YourApp::Application.assets.find_asset(controller_asset) %>
To include an asset based on controller name and action name (was useful for me)
<% action_asset = "#{controller.controller_name}/#{controller.action_name}" %>
<%= stylesheet_link_tag action_asset if YourApp::Application.assets.find_asset(action_asset) %>
And of course it'd be better not to leave this code as it is, but rather place it in a helper.
= stylesheet_link_tag controller.controller_name if File.exists?(File.join(Rails.public_path, 'assets', "#{controller.controller_name}.css"))
Simple ViewHelpers
This is what I use myself. Add this to your ApplicationHelper:
module ApplicationHelper
def controller_stylesheet(opts = { media: :all })
if Rails.application.assets.find_asset("#{params[:controller]}.css")
stylesheet_link_tag(params[:controller], opts)
end
end
def controller_javascript(opts = {})
if Rails.application.assets.find_asset("#{params[:controller]}.js")
javascript_include_tag(params[:controller], opts)
end
end
end
and use them like this in your application.html.haml:
= controller_stylesheet
= controller_javascript
Note: This works with all .js, .coffee, .css, .scss even though it just says .css and .js
In order to keep controller specific JavaScript logic out of the standard application.js and only have it included by the relevant controller, I'm putting it in its own .js file and including it based on the controller name from the layout like such:
<%= javascript_include_tag "application", params[:controller] %>
That works just fine, but when I deploy the app to production (I'm using Capistrano and have a pre-compile task set up), the asset pipeline doesn't precompile any of the controller specific JS files. I presume this is because my actual JavaScript file isn't referenced by require directives in application.js.
How do I deal with this without moving my controller specific JS back to application.js, or explicitly referencing it from application.js?
Is there some way to tell the asset pipeline to pre-compile an additional list files? How could I manually pre-compile a specific file on production?
Update
As it turns out, you can specify individual files here in your config/environments/production.rb:
config.assets.precompile += %w( achievements.js )
...or I just went ahead and capriciously added it for every JavaScript file:
config.assets.precompile += %w( *.js )
If you want to precompile the js|css only found in the root of assets/javascripts and assets/stylesheets directories (and not their tree hierarchy), you can put this in environment files :
Dir.chdir "#{Rails.root}/app/assets/javascripts"
a = Dir.glob("*.{js,coffee,erb}")
Dir.chdir "#{Rails.root}/app/assets/stylesheets"
b = Dir.glob("*.{css,erb}")
config.assets.precompile += a.concat(b)
Dir.chdir Rails.root
I think you and james_schorr are not really talking about the same thing.
You need to add the files other than application.js to config.assets.precompile. His answer was more about the directory structure you could/should adopt, if I'm not mistaken.
If I wanted to have controller specific, I would do:
/assets
/javascripts
/users
login.js
profile.js
/blogs
/posts
users.js
blogs.js
posts.js
And for instance, users.js would be:
*= require_tree ./users
That way, you can stay organized (have a lot of js files per controller), but in prod, they will all be included in one file.
Still need that in your config:
config.assets.precompile += %w( *.js )
This is what I do:
directory structure:
app/assets/javascripts/sessions/multiple.js
app/assets/application-sessions.js
application-sessions.js just has:
*= require_self
*= require_tree ./sessions
Then in your view, do
<% if #current_controller == 'whatever' %>
<%= javascript_include_tag "application-sessions" %>
<% else %>
….
<% end %>
FYI, #current_controller = controller_name in my application_controller.rb methods, called with a before_filter.
I am having same issue here, it's an headache to me.
Including *.js is a little too much, I don't want every files to be compiled into separated files, some of them suppose to be merged together.
My idea is, store the controller specific files into a sub directory, such as "controllers", and then only include controllers/*.js or css files for precompile.
Not sure if it work or not, I will try it out, anyway, it might be a useful hint.
I have two RoR3 applications (APP1 and APP2)
www.subdomain1.example.com
www.subdomain2.example.com
and I want to show on APP1 some views from APP2.
I tried to do that using a 'Net::HTTP' request (code in APP1)
Net::HTTP.get( URI.parse("http://www.subdomain2.example.com/users/new") )
but the response is not evaluated as HTTP code. Among other things I do not know if there are other techniques to do what I want in more easy way.
So, is it possible to render partials from APP1 to APP2 using the common and easy approach of rendering partials in the same RoR application?
Example:
render :partial => "/users/new"
If so, how can I do that?
Here, try this:
module ApplicationHelper
require 'open-uri'
def render_url(url)
open url do |f|
f.read.html_safe # remove the 'html_safe' if you're on Rails 2.x
end
end
end
In your view:
<%= render_url 'http://ilikestuffblog.com/' %>
It will work. Just one problem, though: if the site contains relative links to images, other pages, or anything else, those links will not be shown correctly. Try this to see a bunch of blank images:
<%= render_url 'http://www.ducklet.com/' %>
Also, BE WARNED that if you don't own the URL you're including, you will be subject to cross-site scripting weirdness.
If the two applications share a filesystem or have access to a shared filesystem, then you can reference a partial directly by file path. From the Rails guide on rendering:
2.2.4 Rendering an Arbitrary File
The render method can also use a view
that’s entirely outside of your
application (perhaps you’re sharing
views between two Rails applications):
render
"/u/apps/warehouse_app/current/app/views/products/show"
Rails determines that this is a file
render because of the leading slash
character. To be explicit, you can use
the :file option (which was required
on Rails 2.2 and earlier):
render :file =>
"/u/apps/warehouse_app/current/app/views/products/show"
The :file option takes an absolute
file-system path. Of course, you need
to have rights to the view that you’re
using to render the content.
It might be more prudent to create a gem that has any shared code (ie. partials) in it so both apps can use it.