Forcing rails to create asset cache files - ruby-on-rails

I am hosting my assets on s3. In production, rails is looking for /javascripts/cache/all.js and /stylesheets/cache/all.css. I'm using a plugin to swoop the public directory over to s3 when I deploy with cap. The problem is that rails doesn't create these cache files until they are requested for the first time, so they aren't around during deployment when I transfer the public dir. Is there an easy way to force the creation of these files during deployment?

The actual rails module that creates the cache files is ActionView::Helpers::AssetTagHelper which you may be able to re-use to create the cache files during deployment. The following worked okay for me:
require 'action_view'
class AssetCacheWriter
include ActionView::Helpers::AssetTagHelper
def write
write_asset_file_contents(File.join(JAVASCRIPTS_DIR, "all.js"), compute_javascript_paths([:all], true))
write_asset_file_contents(File.join(STYLESHEETS_DIR, "all.css"), compute_stylesheet_paths([:all], true))
'standard_all')
end
end
Both those write_asset_file_contents calls I've lifted from the rails code. The _DIR constants are defined in the included module and the true parameters in the compute_xxx_paths calls indicate that all files should be included recursively.
I created a simple cap task that just wrote the files out:
namespace :sample
task :assets do
AssetCacheWriter.new.write
end
end

I use this in a rake task file. Saves you from creating an additional class and you keep it DRY because you're using the values from the stylesheet_link_tag / javascript_include_tag
desc "Clears javascripts/cache and stylesheets/cache" task :clear => :environment do
puts "Clearing javascripts/cache and stylesheets/cache"
FileUtils.rm(Dir['public/javascripts/cache_[^.]*']) # use :cache => 'cache_all.js' in stylesheet_link_tag
FileUtils.rm(Dir['public/stylesheets/cache_[^.]*']) # use :cache => 'cache_all.css' in javascript_include_tag
end
desc "Recreate the javascripts/stylesheets cache."
task :generate => [:environment, :clear] do
puts "Recreate the javascripts/stylesheets cache"
ActionController::Base.perform_caching = true
app = ActionController::Integration::Session.new
app.get '/'
end

If you're using Engine Yard Cloud, this is what I did:
added a file /lib/tasks/assetcache.rake
require 'assetwriter'
namespace :assetcache do
desc "Clears javascripts/cache and stylesheets/cache"
task :clear => :environment do
puts "Clearing javascripts/cache and stylesheets/cache"
FileUtils.rm(Dir['public/javascripts/cache_[^.]*'])
# use :cache => 'cache_all.js' in stylesheet_link_tag
FileUtils.rm(Dir['public/stylesheets/cache_[^.]*'])
# use :cache => 'cache_all.css' in javascript_include_tag
end
desc "Recreate the javascripts/stylesheets cache."
task :generate => [:environment, :clear] do
puts "Recreate the javascripts/stylesheets cache"
AssetCacheWriter.new.write
end
end
then I added /lib/assetwriter.rb
require 'action_view'
class AssetCacheWriter
include ActionView::Helpers::AssetTagHelper
def write
write_asset_file_contents(File.join(JAVASCRIPTS_DIR, "cache/all.js"), compute_javascript_paths([:all], true))
write_asset_file_contents(File.join(STYLESHEETS_DIR, "cache/all.css"), compute_stylesheet_paths([:all], true))
end
end
then I added to /deploy/after_restart.rb
run "cd #{release_path} && bundle exec rake assetcache:generate"

I found that include order mattered (so, for example, jQuery is included before other scripts) and so used the below approach instead. Also allows for the use of :defaults.
First lib/asset_pacakger:
require 'action_view'
class AssetCacheWriter
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::TagHelper
def write
ActionController::Base.perform_caching = true
stylesheet_link_tag *ApplicationController::STYLESHEET_INCLUDES
javascript_include_tag *ApplicationController::JAVASCRIPT_INCLUDES
end
end
Then scripts/package_assets:
#!/usr/bin/env ruby
RAILS_ROOT = File.expand_path(File.dirname(__FILE__) + '/../')
Dir.chdir(RAILS_ROOT)
RAILS_ENV = ENV['RAILS_ENV'] || 'development'
puts "Bundling assets for environment #{RAILS_ENV}"
require File.join('config', 'environment')
AssetCacheWriter.new.write
Then a cap task:
desc "Package assets"
task :package_assets do
%x[#{Dir.getwd}/script/package_assets]
%x[svn commit #{Dir.getwd}/public/javascripts/defaults.js #{Dir.getwd}/public/stylesheets/defaults.css -m "(capistrano) packaging assets to trunk"]
logger.info "packaged assets to trunk"
end

In Rails 3.0.7 none of the above approaches seemed to work. I got many errors about config being undefined.
After getting my hands dirty looking over the Rails code, I came up with this solution that is working for me.
lib/tasks/cache_assets.rake
require 'asset_packager'
desc "cache assets"
task :cache_assets do
puts "-----> Caching Assets"
AssetCacheWriter.new.write
puts "-----> Done!"
end
And then lib/asset_packager.rb
require 'action_view'
class AssetCacheWriter
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::TagHelper
def config
ApplicationController
end
def controller
ApplicationController
end
def write
ActionController::Base.perform_caching = true
# You can replace these with your app's cached assets. They should look the same
# as in your view templates.
stylesheet_link_tag :vendor, :defaults, :cache => 'cached/all'
javascript_include_tag :bd_defaults, :'vendor-head', :cache => 'cached/defaults_vendor'
end
end
Then to execute it, just run rake cache_assets

Related

Rails 4.2 run rake task after ActiveRecord load

I have some data, which I want to pre-load in my add on booting. I've made an rake task and it works good. I tried to put code in config/initializers but it starts too early (I need all models to be loaded). after_initialize is not good too for me. I place code example lower
require 'rake'
load File.join(Rails.root, 'lib', 'tasks', 'rights.rake')
Rake::Task['dev:create_rights'].invoke
So, where is good place to put this code? Of course I can put it in AR::Base or so on, but it is ugly.
Here is the task, if It will help.
namespace :dev do
desc "Creation of the minimal rights"
task :create_rights => :environment do
klasses = ActiveSupport::DescendantsTracker.descendants(AbstractModel)
default_rights = RightsList.default_rights
Role.includes(:rights).all.each do |role|
klasses.reject{|klass| role.rights.pluck(:klass_name).include? klass.underscore }.each do |klass|
Right.create role: role, klass_name: klass.underscore, rights_per_class: default_rights
end
end
end
end
Thank you
UPDATE
Got dirty solution with adding in config/application.rb
config.after_initialize do
require 'rake'
load File.join(Rails.root, 'lib', 'tasks', 'rights.rake')
Rails.application.eager_load!
Rake::Task['dev:create_rights'].invoke
end
And I understand that it is still wrong way. Is here good way?

can i add rake task to my rails app by making gem?

I want to add original rake task to my rails app by making gem. But, I couldn't.
Please tell me my mistake.
In my gem file
>lib/gem_test.rb
require "gem_test/version"
require 'gem_test/railtie' if defined?(Rails) && Rails.version >= "3"
module GemTest
#this is dummy method for loading check
def self.hogehoge
"hogehoge"
end
end
>lib/gem_test/railtie.rb
module GemTest
class Railtie < Rails::Railtie
rake_tasks do
load "tasks/gem_test.rake"
end
end
end
>lib/tasks/gem_test.rake
namespace :fugafuga do
desc "fugafuga"
task :piyopiyo do
puts "piyopoyo"
end
end
and, Gemfile in my rails app,gem gem_test, git:mygitrepository. But I don't find fugafuga:piyopiyotask in result of rake -T.
If need more infomation, see my repository
I think you must require rake in your rakefile:
namespace :fugafuga do
require 'rake' <----
desc "fugafuga"
task :piyopiyo do
puts "piyopoyo"
end
end

How do I access one of my models from within a ruby script in the /lib/ folder in my Rails 3 app?

I tried putting my script in a class that inherited from my model, like so:
class ScriptName < MyModel
But when I ran rake my_script at the command-line, I got this error:
rake aborted!
uninitialized constant MyModel
What am I doing wrong?
Also, should I name my file my_script.rb or my_script.rake?
Just require the file. I do this in one of my rake tasks (which I name my_script.rake)
require "#{Rails.root.to_s}/app/models/my_model.rb"
Here's a full example
# lib/tasks/my_script.rake
require "#{Rails.root.to_s}/app/models/video.rb"
class Vid2 < Video
def self.say_hello
"Hello I am vid2"
end
end
namespace :stuff do
desc "hello"
task :hello => :environment do
puts "saying hello..."
puts Vid2.say_hello
puts "Finished!"
end
end
But a better design is to have the rake task simply call a helper method. The benefits are that it's easier to scan the available rake tasks, easier to debug, and the code the rake task runs becomes very testable. You could add a rake_helper_spec.rb file for example.
# /lib/rake_helper.rb
class Vid2 < Video
def self.say_hello
"Hello I am vid2"
end
end
# lib/tasks/myscript.rake
namespace :stuff do
desc "hello"
task :hello => :environment do
Vid2.say_hello
end
end
All I had to do to get this to work was put my requires above the task specification, and then just declare the :environment flag like so:
task :my_script => :environment do
#some code here
end
Just by doing that, gave me access to all my models. I didn't need to require 'active_record' or even require my model.
Just specified environment and all my models were accessible.
I was also having a problem with Nokogiri, all I did was removed it from the top of my file as a require and added it to my Gemfile.

Rails 3.1: how to run an initializer only for the web app (rails server/unicorn/etc)

My webapp needs to encrypt its session data. What I setup is:
config/initializers/encryptor.rb:
require 'openssl'
require 'myapp/encryptor'
MyApp::Encryptor.config[ :random_key ] = OpenSSL::Random.random_bytes( 128 )
Session.delete_all
app/models/session.rb:
require 'attr_encrypted'
class Session < ActiveRecord::Base
attr_accessible :session_id, :data
attr_encryptor :data, :key => proc { MyApp::Encryptor.config[ :random_key ] }, :marshal => true
# Rest of model stuff
end
That all works great, and keeps the session data secured. Here's the problem: when I run my custom rake tasks it loads the initializer and clears all the sessions. Not good!
What can I put in my initializer to make sure it ONLY runs for the webapp initialization? Or, what can I put in my initializer to make it NOT run for rake tasks?
Update: OK, what I've done for the moment is add MYAPP_IN_RAKE = true unless defined? MYAPP_IN_RAKE to my .rake file. And then in my initializer I do:
unless defined?( MYAPP_IN_RAKE ) && MYAPP_IN_RAKE
# Web only initialization
end
Seems to work. But I'm open to other suggestions.
You might make a modification to your application in `config/application.rb' like this:
module MyApp
def self.rake?
!!#rake
end
def self.rake=(value)
#rake = !!value
end
Then in your Rakefile you'd add this:
MyApp.rake = true
It's nice to use methods rather than constants since sometimes you'd prefer to change or redefine them later. Plus, they don't pollute the root namespace.
Here's a sample config/initializers/rake_environment_test.rb script:
if (MyApp.rake?)
puts "In rake"
else
puts "Not in rake"
end
The programmable nature of the Rakefile affords you significant flexibility.
There is another work around:
unless ENV["RAILS_ENV"].nil? || ENV["RAILS_ENV"] == 'test'
When you launch with rake your ENV["RAILS_ENV"] will be nil. The test for 'test' is to avoid to run when using rspec.
I know that is reckon to use Rails.env but it return "development" when it is not initialised.
http://apidock.com/rails/Rails/env/class
# File railties/lib/rails.rb, line 55
def env
#_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"]
|| ENV["RACK_ENV"] || "development")
end

Rails plugin require issues

I have a problem creating a Rails plugin, lets call it Mplug. The plugin is pretty much only a rake task, but with a library that the rake task uses.
The problem is to require files. Lets say that this is the rake task:
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
This will not recognize the constant Mplug. So I thought I needed to require it.
require 'mplug'
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
But then I get this message.
no such file to load -- mplug
So, ok. Lets try to give the path to the plugin then.
require 'vendor/plugins/mplug/lib/mplug'
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
This actually works. However, except that I guess that this is a bad way to do it, I now have to require the files in my plugin as if I was in the rails root. For example:
module Mplug
end
require 'mplug/indexer'
Now has to be:
module Mplug
end
require 'vendor/plugins/mplug/lib/mplug/indexer'
Which I do not want to do of course.
Is there any neat way to solve this?
Thanks!
The easiest solution to this problem would be to register the rake task using the Rails::Railtie API. In lib/mplug.rb, define your Railtie:
module Mplug
class Railtie < ::Rails::Railtie
rake_tasks do
load "mplug/rails.rake"
end
end
end
Then, in lib/mplug/rails.rake:
namespace :mplug do
task :create do
Mplug::Indexer.new
end
end
Then, make sure your plugin is defined in your Gemfile. If your plugin is in vendor/plugins, add this line to your Gemfile:
gem "mplug", :path => "vendor/plugins/mplug"
If you push the plugin to a git repo, use :git.
Now, rake mplug:create will be available! If you want it to show up in rake -T, make sure you add a description:
namespace :mplug do
desc "creating an mplug"
task :create do
Mplug::Indexer.new
end
end
One option is to use the FILE constant, and then provide the rest of the path relative to the current file:
require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'mplug')
(if your rake task file is in your plugin_root/tasks...)

Resources