Is there a way to always run the ERB preprocessor on a Javascript file?
I'm using Mustache to use the same templates on the client and server. I'd like to include these templates in my application.js files so they're available on the client. So I'm preprocessing my Javascript file (templates.js.erb, which then gets required in application.js) with erb:
App.templates.productShow = <%= MustacheController.read("product/show").to_json %>;
This works great but when I edit the "product/show.html.mustache" template I need to also edit "templates.js.erb" so Rails knows to recompile this file which then picks up the latest changes from the mustache template.
There's no issue running this in production since the assets get compiled when I deploy, but it's annoying when developing. Ideally I could set the preprocessor to run on "templates.js.erb" every time I reload. My current solution is to inline the Javascript in the application layout but it would be nice to keep it separate.
I ended up writing a guardfile for this that adds a timestamp to the end of the file. Just touching the file is enough for sprockets to recompile but if you're using git you need to actually alter the file. Otherwise anyone else who pulls in the code won't get the latest preprocessed files. Here's the code...
#!/usr/bin/ruby
guard 'mustache' do
watch(%r{app/templates/.+\.mustache})
end
require 'guard/guard'
module ::Guard
class Mustache < ::Guard::Guard
def run_on_change(paths)
# file to be updated when any mustache template is updated
file_name = "./app/assets/javascripts/templates.js.erb"
# get array of lines in file
lines = File.readlines(file_name)
# if last line is a comment delete
lines.delete_at(-1) if lines[-1].match /\/\//
# add timestamp
lines << "// #{Time.now}"
# rewrite file
File.open(file_name, "w") do |f|
lines.each{|line| f.puts(line)}
end
end
end
end
This seems to be a common complaint with the pipeline - that you have to touch a file that references variables for those changes to be reflected in development mode. A few people have asked similar questions, and I do not think there is a way around it.
Forcing Sprockets to recompile for every single request is not really a viable solution because it takes so long to do the compilation.
Maybe you could set up guard to watch your mustache directory and recompile templates.js.erb when you make changes to it. Similar to how guard-bundler watches your Gemfile and rebundles on change.
Related
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
I've got somefile.js.coffee.erb file which is processed by Rails asset pipeline. My ERB code returns some string that cannot be parsed by Coffee which result in SyntaxError exception. I would like to peek into generated somefile.js.coffee file, or in general any intermediary file processed by asset pipeline.
I've tried to examine Sprockets with no luck:
environment = Sprockets::Environment.new
MyApplication::Application.config.assets.paths.each {|p| environment.append_path p}
rerb = environment['somefile.js.coffee.erb']
rerb.source #=> it's already preprocessed
Or to look into \tmp\cache\assets but there are also only preprocessed files, additionaly obscured by fingerprinted name.
Maybe there is a way to hook into asset-pipeline I have no idea how..
Why I need ERB? To generate client-side-model stubs with fields and validations matching Rails model using KnockoutJS (https://github.com/dnagir/knockout-rails extended -> https://github.com/KrzysztofMadejski/knockout-rails).
I am using Rails '~> 3.2.12', sprockets (2.2.2).
Edit: I've ended up injecting erb code in ### comments, to sneak-peak at generated code while coffeescript file is still compiling:
###
<%= somefun() %>
###
Altough I would suggest using #Semyon Perepelitsa answer as it produces coffee script file as it is seen by coffee compiler.
Just remove "coffee" from the file extension temporarily: somefile.js.erb. You will see its intermediate state at /assets/somefile.js as it won't be processed by CoffeeScript.
I wonder if you can put <% binding.pry %> just before the line and mess around till you get it right. Never tried during a compile and don't use coffeescript. In theory, it should work (or is worth a shot) so long as you put gem pry in your Gemfile and run bundle first.
I'm writing a command line command but want to TDD it. I'll be creating and deleting files and was wondering if there's a sandbox testing gem or something like that. I'm using ruby and rspec.
Depends on what you're trying to do, but I test most of my command line Ruby by mocking out the file system and STDIN/STDOUT. Using dependency injection I often end up with something along these lines:
describe Add do
it 'writes the result to standard out' do
console = mock('STDOUT')
console.should_receive(:puts).with('5')
Add.new(console).execute(3,2)
end
end
class Add
def initialize(out = STDOUT)
#out = out
end
def execute(command_line_args)
#out.puts(command_line_args.inject(:+))
end
end
Add.new.execute(ARGS)
By using default values I can inject in the test, but leave it out of the production code.
Hope that helps!
Brandon
The template generated by the newgem install_cucumber generator uses a pattern that I like quite a bit. Have a look at the support/env.rb and support/common.rb files it creates:
https://github.com/drnic/newgem/blob/master/rubygems_generators/install_cucumber/templates/features/support/env.rb.erb
https://github.com/drnic/newgem/blob/master/rubygems_generators/install_cucumber/templates/features/support/common.rb
Use of it in test looks like this:
in_tmp_folder do
# The current directory is now a generated tmp folder.
# If you stick to relative paths, everything you do in here should be safe
end
The files linked to above are for using this in cucumber tests, but it could easily be adapter to whatever framework you're using. The env.rb above deletes the tmp folder before each test starts.
You might also want to take a look at the sandbox gem.
gem install sandbox
Example usage is here: https://github.com/bdimcheff/sandbox
I am working on a Rails 3.1 app. I have created an application.css.scss.erb file. The .erb is in the end because I want to load a variable from the config file as the color variable in the css:
$highlight1: #<%= COLOR.highlight1 %>;
$highlight2: #<%= COLOR.highlight2 %>;
Everything works fine, but the problem I am having is that whenever I change a value inside COLOR.highlight1, it doesn't reflect the change until I go in to my css file and change something (i usually add some spaces and save it). Thats when I see the change. Clearly rails is looking to see if the file was changed in order to update the change.
Is there any way that at least during development, this can be turned off and I can see the changes without having to also modify the css file?
Any critique/opinions on my technique are also welcome
The Sprockets depend_on directive is used to declare these kinds of dependencies. So at the top of your css.scss.erb file, with the other directives (require and friends), put something like:
//= depend_on "/path/to/colors.rb"
Then when the file /path/to/colors.rb changes, it will force the css to update too.
Unfortunately, I have never gotten this to work with a relative path to a file outside of one of the asset directories (javascripts/stylesheets/images) so there may be something in the way Sprockets resolves paths that prevents this, or else I'm missing something. That leaves you with the options of specifying an absolute path, which will almost certainly not work in across all your app environments, or putting the constants file into your asset directories (app/assets/stylesheets/colors.rb, for example).
For reference, here's the doc for the depend_on directive from the Sprockets (2.0.3) source, in sprockets/directive_processor.rb
# Allows you to state a dependency on a file without
# including it.
#
# This is used for caching purposes. Any changes made to
# the dependency file will invalidate the cache of the
# source file.
#
# This is useful if you are using ERB and File.read to pull
# in contents from another file.
#
# //= depend_on "foo.png"
#
If anyone does know a way to specify relative paths to other places like config/initializers or something, please let me know!
In addition to David Faber's answer. I needed to use relative paths too.
I wanted to generate a js file with the locale dictionary, which would update if the locale files were changed:
//= depend_on "../../../config/locales/en.yml"
//= depend_on "../../../config/locales/ja.yml"
var locales = <%= locales.to_json %>;
Turns out that currently (Rails 3.2.3) relative paths only work if the relative path is also in the assets path!
So the ugly solution is to add the path in config/application.rb:
config.assets.paths.unshift Rails.root.join("config", "locales").to_s
http://guides.rubyonrails.org/configuring.html
config.assets.compile is a boolean that can be used to turn on live Sprockets compilation in production.
might want to try that, I'm not sure if its getting compiled real time though, at least it should disable the caching.
maybe try:
config.assets.digest = true
in your development config file
I try this, it work
in application.rb
config.autoload_paths += %W(#{config.root}/lib/assets_variables)
config.assets.paths << File.join(Rails.root, 'lib', 'assets_variables')
in lib/assets_variables/color.rb
module Color
def self.default
'blue'
end
end
in app/assets/stylesheets/color.css.scss.erb
//= depend_on "color.rb"
$default_color: <%= Color::default %>;
.content {
color: $default_color;
}
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.