Live assets compilation only for one file in Rails - ruby-on-rails

There is config.assets.compile=true parameter which enables so called "live compilation".
I need Rails to compile one specific asset (sample.css.scss.erb) on each request in live mode but the rest of assets should be still precompiled.
How can I achieve this?

Option 1 - put sample.css.scss.erb in a path different from config.assets.manifest (default="public/assets") and add it with javascript_include_tag
Option 2 - Remove it from the config.assets.precompile
config.assets.precompile -= %w( sample.css.scss.erb )
Make sure you clean then precompile to test it.
I did not test either option, please let us know if any works for you.

Live compilation for file is required when css is preprocessed by .css.erb file.
It means dynamically compiling css.
For example : allowing users to set the colors and would like to conditionally use "live compilation"
In that case i will suggest dynamically rendering /users/:id/styles.css.erb instead of live compilation.
Reason there might be millions of users and in production environment using Ec2 instance will cost you more. That's why i will suggest below solution:
First define your custom action on the UsersController:
# config/routes.rb
match '/users/:id/styles' => 'users#styles', :as => :user_styles
Link to the "stylesheet" in your layout:
# app/views/layouts/application.html.erb
= stylesheet_link_tag 'application', user_styles_path(current_user, :format => 'css')
Define the action in your controller. You could do whatever you want to retrieve the user's preferences here:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def styles
#colors = User.find(params[:id]).colors
end
end
That action will automatically render this view:
# app/views/users/styles.css.scss.erb
$background-color: <%= #colors[:background] %>;
body {
background-color: $background-color;
}
More ideas and suggetions are most welcome.

Related

How to customize devise form css in rails

Command rails generate devise:views successfully created folders under \app\views\users
I am looking to customize the devise forms but not sure whether the css to be placed in application.css or i need to separately create user.css.scss. Googled a bit and check git doc for this but none is specifying for CSS handling in devise.
Let me know the correct way of handling it
Devise will use you default layout. So the CSS that you are using in your views/layouts/application.html.erb will be used in your generated devise views.
If you want devise specific layouts, you can create a views/layouts/devise.html.erb file where you can serve devise specific CSS. It will pick it up automatically because of Rails naming conventions.
The above will work for any controller, just add a file in layouts named after the controller eg. views/layouts/reservations.html.erb for ReservationsController
You can also add specific layouts for the Devise::RegistrationsController by making a directory views/layouts/devise and adding views/layouts/devise/registrations.html.erb
If you are doing controller-specific stylesheets, that is, if you switched off automatic compilation of all of your stylesheets into application.css, you should name your stylesheet after a devise controller, e.g.:
registrations.css.scss
and add it to Rails.application.config.assets.precompile list in assets.rb.
The placement of the stylesheet is standard, app/assets/stylesheets/ so watch for name collisions between devise's controllers and yours.
To pick up a controller-specific stylesheet in a layout you need something like this:
Rails up to 4.1
<%= stylesheet_link_tag controller_name, media: 'all' if Rails.application.assets.find_asset("#{controller_name}.css") %>
Rails 4.2+
<%= stylesheet_link_tag controller_name, media: 'all' if asset_present?("#{controller_name}.css") %>
# ...meanwhile, somewhere in the helpers...
def asset_present?(name)
# Rails 4.1 had Rails.application.assets filled out in all environments.
# Rails 4.2 has it filled only when config.assets.compile == true which is only
# in development by default.
if Rails.application.assets.present?
Rails.application.assets.find_asset(name)
else
Rails.application.assets_manifest.files.values.map { |v| v['logical_path'] }.include?(name)
end
end
To generate device view run this line
rails generate devise:views
and do what ever you want with page styling.
to read more Click here

Rails static html template files in the asset pipeline and caching in development mode

I'm building a website using AngularJS and Rails. The HTML files that I'm using for templates are stored under /app/assets/templates and each time I update a route or change something inside of a nested partial inside of a template I need to "touch" the highest level file in the /app/assets/templates directory for the html file I'm changing.
So if I have a page "edit.html" which loads a partial "_form.html" then whenever I update a route or change something in _form.html I need to make sure that edit.html is touched.
This is annoying and very finicky. Is there any way to inform the asset pipeline/sprockets to avoid caching for the app/assets/templates directory?
The best solution I've found to this is not to use the asset pipeline for HTML template files.
Instead make a controller called TemplatesController and create only one action.
Then map all template URLs to that using a route such as:
get /templates/:path.html => 'templates#page', :constraints => { :path => /.+/ }
Then move all the template files into app/views/templates
Then inside the controller, setup the following:
caches_page :page
def page
#path = params[:path]
render :template => 'templates/' + #path, :layout => nil
end
This way all of your template files will be served from the controller and then will be cached into public/templates. To avoid cache problems, you can create a timestamp path into the template route so that your cached files are delivered with a version:
get '/templates/:timestamp/:path.html' => 'templates#page', :constraints => { :path => /.+/ }
This way you can have a new timestamp each time you upload the website and you can store the templates folder anywhere you like. You can even store the templates folder on S3 and have an assets URL for that. Then wherever your template files are addressed, you can use a custom asset method:
templateUrl : <%= custom_asset_template_url('some/file.html') %>
Where:
def custom_asset_template_url(path)
"http://custom-asset-server.website.com/templates/#{$some_global_timestamp}/#{path}"
end
Then just make the asset redirect to the Rails server if it's not found and it will be generated. Or all template files can be pre-generated once uploaded.
There's a much (much!) better way to deal with this.
<%= path_to_asset("template_name.html") %>
That will return a fully working file from the asset pipeline, which can use ERB, etc. It's undocumented, but it's a part of sprockets / the asset pipeline.
To my mind, several things are needed here:
The templates should be namespaced, so people templates go in the app/views/people/templates directory
The templates are entirely static, so no before filters should be called.
The templates should be cached, making them very fast.
Here's a possible solution using a Rails concern:
# Allows static content to be served from the templates
# directory of a controller
module HasTemplates
extend ActiveSupport::Concern
included do
# Prepend the filter
prepend_before_filter :template_filter, only: [:templates]
# Let's cache the action
caches_action :templates, :cache_path => Proc.new {|c| c.request.url }
end
# required to prevent route from baulking
def templates;end
# Catch all template requests and handle before any filters
def template_filter
render "/#{params[:controller]}/templates/#{params[:template]}", layout: 'blank'
rescue ActionView::MissingTemplate
not_found layout: 'blank'
false
end
end
Notice we are returning the template in a prepended filter. This allows us to return the static content without hitting any other filters.
You can then create a route, something like this:
resources :people do
collection do
get 'templates/:template' => 'people#templates', as: :templates
end
end
Your controller becomes simply:
class PeopleController < ApplicationController
include HasTemplates
end
Now any file placed in the /app/views/people/templates can be served at speed from a url.
Expanding on RandallB's answer a bit; this is mentioned explicitly in the documentation on the Asset Pipeline: http://guides.rubyonrails.org/asset_pipeline.html
Note that you have to append the extension .erb to your .coffee file to have this work. (e.g., application.js.coffee.erb)
You can try gem js_assets (https://github.com/kavkaz/js_assets).
This allows you to function asset_path in javascript code that emulates a similar method of sprockets.
Cleanest solution is to precompile your html assets using ejs and serve them as other javascript files.
No http query
minification
Possibility to pass js data to you templates to make them dynamic (ejs is a port from underscore template and basically
behave like erb for javascript)
It basically work like that :
#in you gemfile
gem 'ejs'
#in app/assets/javascripts/templates/my_template.jst.ejs
<p>my name is <%= name %> !<p>
#in your application.coffee
#= require_tree ./templates
JST['templates/my_template'](name: 'itkin')
=> '<p>my name is itkin !<p>'

how should I include a coffeescript file on only one page?

Edit: a year later if I was going to do this again I'd do it with curl.js instead of Rails asset pipeline.
Related: Best way to add page specific javascript in a Rails 3 app?
I'm writing an app and using coffeescript to generate all of the js. That's why the related question doesn't do what I need.
I'd like to be able to put a coffeescript file in a subfolder of my assets directory and have that .coffee file only get served up on one page. The page is on a named route
match 'myNotifications' => 'user#notifications'
The most obvious thing to do was to put the .coffee file in assets\javascripts\user\index.js.coffee. But after reading the docs about assets I'm unclear.
I read this line (from http://guides.rubyonrails.org/asset_pipeline.html):
You should put any JavaScript or CSS unique to a controller inside
their respective asset files, as these files can then be loaded just
for these controllers with lines such as <%= javascript_include_tag
params[:controller] %> or <%= stylesheet_link_tag params[:controller]
%>.
Ok cool, so I put the page specific js in assets\javascripts\user.js.coffee. Then I reloaded my home page, Ctrl F5. The user.js file is still being loaded on the homepage. Tested with $ -> alert 'ready from users controller'; seeing that alert when I load the homepage.
Does Rails have a way to have a per-page coffeescript file that will only be served up with that page? Am I reading the manual wrong? Is there a place in the assets folder that I can put .coffee files where they won't get loaded with every page?
Update: Looks like I might have an answer:
There are a couple of ways that we can get around this problem. We
could use require_directory instead of require_tree as this will only
load the files in the current directory and not in subdirectories. If
we want more control over the included files we can require them
separately instead of including the whole directory. Alternatively we
could move the JavaScript files that we want to be included on all
pages into a public subdirectory. We can then use require_tree
./public to include just those files.
I'll give that a shot in the AM.
Here's the approach I use to make controller/view specific Coffee:
application.html.haml:
%body{ :data => { :controller => params[:controller], :action => params[:action]} }
alt. application.html.erb
<%= content_tag(:body, :data => { :controller => params[:controller], :action => params[:action] }) do %>
...
<% end %>
application.js.coffee:
$(document).ready ->
load_javascript($("body").data('controller'),$("body").data('action'))
load_javascript = (controller,action) ->
$.event.trigger "#{controller}.load"
$.event.trigger "#{action}_#{controller}.load"
users.js.coffee
$(document).bind 'edit_users.load', (e,obj) =>
# fire on edit users controller action
$(document).bind 'show_users.load', (e,obj) =>
# fire on show users controller action
$(document).bind 'users.load', (e,obj) =>
# fire on all users controller actions
Sidenote:
This works great with PJAX as well as you can pass the controller/action names with the response header on PJAX requests and just fire these js functions based on that.
EDIT (2014/03/04):
This solution still works when using turbolinks.js.
Rather than only including the file on one page, you might want to just use logic that's conditional on the page markup. See my answer to a related question. That way, your users don't have to make an additional <script> request for the particular page.
If there's a lot of logic specific to that page (say, 10K+ minified), then yes, split it out. As you suggested in the edit to your question: Rather than doing require_tree . at the root of your javascripts directory, instead create a sub-directory called global and change the top of application.js from
require_tree .
to
require_tree global
Then put your page-specific CoffeeScript file in the root javascripts directory, and point to it with a javascript_include_tag call in that page's template.
include javascript tag into your view template, like show.html.haml
- content_for :javascripts do
= javascript_include_tag 'folder/coffee_file_name'

How can I customize the active admin layout?

I need to customize the active admin layout, but how can I do it?
The active admin layout is not actually defined as a layout file, but is generated programatically. Placing a custom layout in the layout directory will therefore not actually override the default layout.
You can, however, monkey-patch or duck-punch the active admin layout methods inside your application.
The following will add an ie-specific stylesheet to the header:
module ActiveAdmin
module Views
module Pages
class Base < Arbre::HTML::Document
alias_method :original_build_active_admin_head, :build_active_admin_head unless method_defined?(:original_build_active_admin_head)
def build_active_admin_head
within #head do
meta :"http-equiv" => "Content-type", :content => "text/html; charset=utf-8"
insert_tag Arbre::HTML::Title, [title, active_admin_application.site_title].join(" | ")
active_admin_application.stylesheets.each do |path|
link :href => stylesheet_path(path), :media => "screen", :rel => "stylesheet", :type => "text/css"
end
active_admin_application.javascripts.each do |path|
script :src => javascript_path(path), :type => "text/javascript"
end
text_node csrf_meta_tag
text_node "<!--[if lt IE 7]>
<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"admin_ie7.css\ />
<![endif] -->".html_safe
end
end
end
end
end
end
Clearly an ugly solution.
When a view is defined in a gem AND in the rails app, the one defined in the Rails app is served. It's a logic priority.
So if you need to override all or some active admin views, you'll have to copy these in your app and change them as you desire.
Maybe ActiveAdmin does provide a nicer way to do this by now? I don't know.
However here would be an example for a bit cleaner patch for that situation, in my example to add the webpacker gems javascript_pack_tag to my admin area.
module MyApp
module ActiveAdmin
module Views
module Pages
module BaseExtension
def build_active_admin_head
super
within #head do
text_node(javascript_pack_tag('application'))
end
end
end
end
end
end
end
class ActiveAdmin::Views::Pages::Base < Arbre::HTML::Document
prepend MyApp::ActiveAdmin::Views::Pages::BaseExtension
end
(Using rails 5.1.4) I tried two solutions here that involved messing with the active_admin library, and they did not work for me at all. I found my solution in config/initializers/active_admin.rb. I am adding a small amount of bootstrap styling to the default layout. As far as linking to stylesheets, javascripts, etc., it was as simple as adding this to my active_admin.rb, as per the comments therein:
# == Register Stylesheets & Javascripts
#
# We recommend using the built in Active Admin layout and loading
# up your own stylesheets / javascripts to customize the look
# and feel.
#
# To load a stylesheet:
# config.register_stylesheet 'my_stylesheet.css'
config.register_stylesheet 'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css', { integrity: 'sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk', crossorigin: 'anonymous' }
As far as editing that generated layout, I have yet to figure it out, but it could at least be done indirectly via JavaScript and the inclusion of that javascipt in this file via
config.register_javascript 'active_admin_view_tweaks.js', { defer: true }
I am going to be editing class attributes to make my pages responsive with bootstrap, so I might follow something like this geeksforgeeks article to edit the pages with JavaScript after they've loaded.
I don't know if there is a way to edit the generated layout more directly, but this should work for some cases.
You can override the active admin page layout by putting the following code in your config/intializers/active_admin.rb file:
module AdminPageLayoutOverride
def build_page(*args)
within super do
render "shared/your_custom_view_partial"
end
end
end
ActiveAdmin::Views::Pages::Base.send :prepend, AdminPageLayoutOverride
In the above example, I have a custom view file at app/views/shared/_your_custom_view_partial.html.erb location and I am injecting that in all of my active admin pages by the above code.

Rails 3.1 asset pipeline: how to load controller-specific scripts?

If I generate a new controller in Rails 3.1, also a javascript file with the name of the controller will added automatically. Firstly, I thought this javascript file will used only, when the related controller is called.
By default there is the instruction //= require_tree . in the application.js-file, that include every javascript file on it's tree.
How could I load only the controller specific script?
To load only the necessary name_of_the_js_file.js file:
remove the //=require_tree from application.js
keep your js file (that you want to load when a specific page is loaded) in the asset pipeline
add a helper in application_helper.rb
def javascript(*files)
content_for(:head) { javascript_include_tag(*files) }
end
yield into your layout:
<%= yield(:head) %>
add this in your view file:
<% javascript 'name_of_the_js_file' %>
Then it should be ok
An elegant solution for this is to require controller_name in your javascript_include_tag
see http://apidock.com/rails/ActionController/Metal/controller_name/class
<%= javascript_include_tag "application", controller_name %>
controller_name.js will be loaded and is in the asset also, so you can require other files from here.
Example, rendering cars#index will give
<%= javascript_include_tag "application", "cars" %>
where cars.js can contain
//= require wheel
//= require tyre
Enjoy !
I always include this inside my layout files. It can scope your js to action
<%= javascript_include_tag params[:controller] if AppName::Application.assets.find_asset("#{params[:controller]}.js") %>
<%= javascript_include_tag "#{params[:controller]}_#{params[:action]}" if AppName::Application.assets.find_asset("#{params[:controller]}_#{params[:action]}.js") %>
Your problem can be solved in different ways.
Add the assets dynamically
Please consider that this isn't a good solution for the production mode, because your controller specifics won't be precompiled!
Add to our application helper the following method:
module ApplicationHelper
def include_related_asset(asset)
# v-----{Change this}
if !YourApp::Application.assets.find_asset(asset).nil?
case asset.split('.')[-1]
when 'js'
javascript_include_tag asset
when 'css'
stylesheet_link_tag asset
end
end
end
end
Call the helper method in your layout-file:
<%= include_related_asset(params[:controller].to_param + '_' + params[:action].to_param . 'js') %>
Create specific assets for your controller actions. E. g. controller_action.js
Please don't forget to change YourApp to the name of your app.
Use yield
Add <%= yield :head%> to your layout head
Include your assets from your action views:
<% content_for :head do %>
<%= javascript_include_tag 'controller_action' %>
<% end %>
Please see the Rails guides for further information.
I like albandiguer's solution. With which I've found that javascript/coffeescript assets are not individually precompiled. Which causes all sorts of errors trying to use javascript_path. I'll share my solution to that problem after I address an issue a few people mentioned in his comments. Mainly dealing with only a partial set of controller named JavaScript files.
So I built an application helper to detect if the file exists in the javascript directory regardless of .coffee/.js extension:
module ApplicationHelper
def javascript_asset_path(basename)
Sprockets::Rails::Helper.assets.paths.select{|i|
i =~ /javascript/ and i =~ /#{Rails.root}/
}.each do |directory|
if Dir.entries(directory).map {|i| i.split('.')[0]}.compact.
include? basename
return File.join(directory, basename)
end
end
nil
end
end
This method will return the full path to the javascript file if it exists. Otherwise it returns nil. So following Pencilcheck's comment you can add this method for a conditional include:
<%= javascript_include_tag(controller_name) if javascript_asset_path(controller_name) %>
And now you have a proper conditional include. Now for the issue of precompiled assets. Generally for optimization you don't want assets precompiled individually. You can however do it if you must:
# Live Compilation
config.assets.compile = true
You can add this do your environment config file. Test it in your development environment file first. Again this is ill-advisable. The Rails asset pipeline uses Sprockets to optimize everything:
Sprockets loads the files specified, processes them if necessary,
concatenates them into one single file and then compresses them (if
Rails.application.config.assets.compress is true). By serving one file
rather than many, the load time of pages can be greatly reduced
because the browser makes fewer requests. Compression also reduces
file size, enabling the browser to download them faster.
PLEASE READ the documentation for further details of the mechanics of Sprockets (Asset Pipeline) http://guides.rubyonrails.org/asset_pipeline.html
Assets aren't precompiled individually. For example when I try:
<%= javascript_include_tag 'event' %>
I get:
Sprockets::Rails::Helper::AssetFilteredError: Asset filtered out and
will not be served: add Rails.application.config.assets.precompile +=
%w( event.js ) to config/initializers/assets.rb and restart your
server
So you can include which assets to be precompiled individually. We just need to add the relevant controller named javascript files in our asset initializer. Well we can do this programatically.
To get a list of controller names I will use ecoologic's example:
all_controllers = Dir[
Rails.root.join('app/controllers/*_controller.rb')
].map { |path|
path.match(/(\w+)_controller.rb/); $1
}.compact
And now to get the name of all javascript files that match the basename of the controller name you can use the following:
javascripts_of_controllers = Sprockets::Rails::Helper.assets.paths.select{|a_path|
a_path =~ /javascript/ and a_path =~ /#{Rails.root}/
}.map {|a_path|
Dir.entries(a_path)
}.flatten.delete_if {|the_file|
!the_file['.js']
}.collect {|the_file|
the_file if all_controllers.any? {|a_controller| the_file[a_controller]}
}
Then you can try:
# config/initializers/assets.rb
Rails.application.config.assets.precompile += javascripts_of_controllers
This will get you a list of all javascript files, without directory path, that match your controller name. Note if your controller name is plural, the javascript name should be as well. Also note if the controller is singular and the javascript file is plural this will still include it because of the_file[a_controller] will succeed on a partial match.
Feel free to try this out in your Rails.application.config.assets.precompile setting. I know that this gets you the list of files correctly. But I'll leave you to test it. Let me know if there are any nuances involved with precompiling this way as I am curious.
For a very thorough explanation on how assets precompile see this blog: http://www.sitepoint.com/asset-precompile-works-part/
I recently found a simple approach to use generated scripts for specific controller. I use for that solution gem gon. Add in a controller:
class HomesController < ApplicationController
before_filter :remember_controller
private
def remember_controller
gon.controller = params[:controller]
end
end
After that open your homes.js.cofee and add in the beginning of file:
jQuery ->
if gon.controller == "sermons"
# Place all functions here...
That is all.

Resources