Module: AssetHat

Defined in:
lib/asset_hat.rb,
lib/asset_hat/js.rb,
lib/asset_hat/css.rb,
lib/asset_hat/vcs.rb,
lib/asset_hat/railtie.rb,
lib/asset_hat/version.rb,
lib/asset_hat/js/vendors.rb

Overview

Your assets are covered. See README.rdoc for more.

Defined Under Namespace

Modules: CSS, JS Classes: Railtie

Constant Summary collapse

RAILS_ROOT =

:nodoc:

File.join(File.dirname(__FILE__), '..')
TYPES =

Types of supported assets; currently [:css, :js].

[:css, :js]
ASSETS_DIR =

Base directory in which all assets are kept, e.g., ‘public/’.

defined?(Rails.public_path) && Rails.public_path.present? ?
Rails.public_path : 'public'
STYLESHEETS_PATH =

Root URL path for all stylesheets. For public-facing use.

'/stylesheets'
JAVASCRIPTS_PATH =

Root URL path for all JavaScripts. For public-facing use.

'/javascripts'
STYLESHEETS_DIR =

Directory in which all stylesheets are kept, e.g., ‘public/stylesheets’. For internal filesystem use.

File.join(ASSETS_DIR, 'stylesheets')
JAVASCRIPTS_DIR =

Directory in which all JavaScripts are kept, e.g., ‘public/javascripts’. For internal filesystem use.

File.join(ASSETS_DIR, 'javascripts')
RELATIVE_CONFIG_FILEPATH =

Relative path for the config file.

File.join('config', 'assets.yml')
CONFIG_FILEPATH =

Absolute path for the config file.

File.join(RAILS_ROOT, RELATIVE_CONFIG_FILEPATH)
VERSION =

This gem’s version number.

self.version

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.asset_existsObject

:nodoc:



43
44
45
# File 'lib/asset_hat.rb', line 43

def asset_exists
  @asset_exists
end

.configObject

Nested-hash version of config/assets.yml.



43
44
45
# File 'lib/asset_hat.rb', line 43

def config
  @config
end

.html_cacheObject

:nodoc:



43
44
45
# File 'lib/asset_hat.rb', line 43

def html_cache
  @html_cache
end

.last_bundle_commit_idsObject

:nodoc:



3
4
5
# File 'lib/asset_hat/vcs.rb', line 3

def last_bundle_commit_ids
  @last_bundle_commit_ids
end

.last_commit_idsObject

:nodoc:



3
4
5
# File 'lib/asset_hat/vcs.rb', line 3

def last_commit_ids
  @last_commit_ids
end

Class Method Details

.asset_exists?(filename, type) ⇒ Boolean

Returns true if the specified asset exists in the file system:

AssetHat.asset_exists?('application.css', :css)
  # => true if public/stylesheets/application.css exists
AssetHat.asset_exists?('some-plugin.js', :js)
  # => true if public/javascripts/some-plugin.js exists

See also AssetHat.assets_dir.

Returns:

  • (Boolean)


154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/asset_hat.rb', line 154

def self.asset_exists?(filename, type)
  # Process arguments
  type = type.to_sym
  unless TYPES.include?(type)
    raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
    return
  end

  # Default to `{:css => {}, :js => {}}`
  @asset_exists ||= TYPES.inject({}) { |hsh, t| hsh.merge(t => {}) }

  if @asset_exists[type][filename].nil?
    @asset_exists[type][filename] =
      File.exist?(File.join(self.assets_dir(type), filename))
  end
  @asset_exists[type][filename]
end

.assets_dir(type) ⇒ Object

Returns the relative path to the directory where the original CSS or JS files are stored. For internal filesystem use.

type argument: :css or :js



63
64
65
66
67
68
69
70
71
# File 'lib/asset_hat.rb', line 63

def self.assets_dir(type)
  case type.to_sym
  when :css ; STYLESHEETS_DIR
  when :js  ; JAVASCRIPTS_DIR
  else
    raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
    nil
  end
end

.assets_path(type) ⇒ Object

Returns the root URL path where the original CSS or JS files are stored. For external URL-building use.

type argument: :css or :js



77
78
79
80
81
82
83
84
85
# File 'lib/asset_hat.rb', line 77

def self.assets_path(type)
  case type.to_sym
  when :css ; STYLESHEETS_PATH
  when :js  ; JAVASCRIPTS_PATH
  else
    raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
    nil
  end
end

.bundle_filenames(bundle, type) ⇒ Object

Returns the extension-less names of files in the given bundle:

AssetHat.bundle_filenames('application', :css)
  # => ['reset', 'application']
AssetHat.bundle_filenames('non-existent-file', :css)
  # => nil


210
211
212
213
214
215
216
217
218
# File 'lib/asset_hat.rb', line 210

def self.bundle_filenames(bundle, type)
  # Process arguments
  unless TYPES.include?(type.to_sym)
    raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
    return
  end

  self.config[type.to_s]['bundles'][bundle.to_s] rescue nil
end

.bundle_filepaths(bundle, type) ⇒ Object

Returns the full paths of files in the given bundle:

AssetHat.bundle_filenames('application', :css)
  # => ['/path/to/app/public/stylesheets/reset.css',
        '/path/to/app/public/stylesheets/application.css']
AssetHat.bundle_filenames('non-existent-file', :css)
  # => nil


227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/asset_hat.rb', line 227

def self.bundle_filepaths(bundle, type)
  # Process arguments
  unless TYPES.include?(type.to_sym)
    raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
    return
  end

  dir = self.assets_dir(type)
  filenames = self.bundle_filenames(bundle, type)
  filepaths = filenames.present? ?
    filenames.map { |fn| File.join(dir, "#{fn}.#{type}") } : nil
end

.bundles_dir(*args) ⇒ Object

Returns the relative path to the directory where CSS or JS bundles are stored. For internal filesystem use.

Usage:

AssetHat.bundles_dir
  # => 'bundles'
AssetHat.bundles_dir(:ssl => true)
  # => 'bundles/ssl'
AssetHat.bundles_dir(:css)
  # => 'public/stylesheets/bundles'
AssetHat.bundles_dir(:js, :ssl => true)
  # => 'public/javascripts/bundles/ssl

Options:

ssl

Set this to true if the stylesheet references images via SSL. Defaults to false.



105
106
107
108
109
110
111
112
113
# File 'lib/asset_hat.rb', line 105

def self.bundles_dir(*args)
  options = args.extract_options!
  options.symbolize_keys!.reverse_merge!(:ssl => false)
  type = args.first

  dir = type.present? ? File.join(assets_dir(type), 'bundles') : 'bundles'
  dir = File.join(dir, 'ssl') if options[:ssl]
  dir
end

.bundles_path(type, options = {}) ⇒ Object

Returns the root URL path where CSS or JS bundles are stored. For external URL-building use.

Usage:

AssetHat.bundles_path(:css)
  # => 'public/stylesheets/bundles'
AssetHat.bundles_path(:js, :ssl => true)
  # => 'public/javascripts/bundles/ssl

Options:

ssl

Set this to true if the stylesheet references images via SSL. Defaults to false.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/asset_hat.rb', line 129

def self.bundles_path(type, options={})
  type = type.to_sym
  unless TYPES.include?(type)
    raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
    return
  end

  path =  case type
          when :css ; STYLESHEETS_PATH
          when :js  ; JAVASCRIPTS_PATH
          else nil
          end
  path += '/bundles'
  path += '/ssl' if options[:ssl]
  path
end

.cache?Boolean

Returns true if bundles should be included as single minified files (e.g., in production), or false if bundles should be included as separate, unminified files (e.g., in development). To modify this value, set config.action_controller.perform_caching (boolean) in your environment.

Returns:

  • (Boolean)


178
# File 'lib/asset_hat.rb', line 178

def self.cache? ; ActionController::Base.perform_caching ; end

.cache_last_commit_idsObject

Precomputes and caches the last commit ID for all bundles. Your web server process(es) should run this at boot to avoid overhead during user runtime.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/asset_hat/vcs.rb', line 75

def self.cache_last_commit_ids
  AssetHat::TYPES.each do |type|
    next if AssetHat.config[type.to_s].blank? ||
            AssetHat.config[type.to_s]['bundles'].blank?

    AssetHat.config[type.to_s]['bundles'].keys.each do |bundle|
      # Memoize commit ID for this bundle
      AssetHat.last_bundle_commit_id(bundle, type) if AssetHat.cache?

      # Memoize commit IDs for each file in this bundle
      AssetHat.bundle_filepaths(bundle, type).each do |filepath|
        AssetHat.last_commit_id(filepath)
      end
    end
  end
end

.clear_html_cacheObject



294
295
296
# File 'lib/asset_hat.rb', line 294

def self.clear_html_cache
  html_cache = {}
end

.compute_asset_host(asset_host, source, options = {}) ⇒ Object

Reads ActionController::Base.asset_host, which can be a String or Proc, and returns a String. Should behave just like Rails 2.3.x’s private ‘compute_asset_host` method, but with a simulated request.

Example environment config for CDN support via SSL:

# In config/environments/production.rb:
config.action_controller.asset_host = Proc.new do |source, request|
  "#{request.protocol}cdn#{source.hash % 4}.example.com"
    # => 'http://cdn0.example.com', 'https://cdn1.example.com', etc.
end

If your CDN doesn’t have SSL support, you can instead revert SSL pages to serving assets from your web server:

config.action_controller.asset_host = Proc.new do |source, request|
  request.ssl? ? nil : "http://cdn#{source.hash % 4}.example.com"
end

Options:

ssl

Set to true to simulate a request via SSL. Defaults to false.



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/asset_hat.rb', line 263

def self.compute_asset_host(asset_host, source, options={})
  host = asset_host
  if host.is_a?(Proc) || host.respond_to?(:call)
    case host.is_a?(Proc) ?
         host.arity : host.method(:call).arity
    when 2
      if defined?(ActionDispatch)
        request_class = ActionDispatch::Request
      else # Rails 2.x
        request_class = ActionController::Request
      end
      request = request_class.new(
        'HTTPS' => options[:ssl] ? 'on' : 'off')
      host = host.call(source, request)
    else
      host = host.call(source)
    end
  else
    host %= (source.hash % 4) if host =~ /%d/
  end
  host
end

.consider_all_requests_local?Boolean

Returns the value of Rails.application.config.consider_all_requests_local or its equivalent in older versions of Rails. To modify this value, set config.consider_all_requests_local (boolean) in your environment.

Returns:

  • (Boolean)


185
186
187
188
189
190
191
# File 'lib/asset_hat.rb', line 185

def self.consider_all_requests_local?
  if defined?(Rails) && Rails.respond_to?(:application)
    Rails.application.config.consider_all_requests_local
  else # Rails 2.x
    ActionController::Base.consider_all_requests_local
  end
end

.last_bundle_commit_id(bundle, type) ⇒ Object

Usage:

AssetHat.last_bundle_commit_id('application', :css)

Returns a string of the latest commit ID for the given bundle, based on which of its files were most recently modified in the repository. If no ID can be found, ‘nil` is returned.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/asset_hat/vcs.rb', line 45

def self.last_bundle_commit_id(bundle, type)
  # Process arguments
  type = type.to_sym
  unless TYPES.include?(type)
    raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
    return
  end

  # Default to `{:css => {}, :js => {}}`
  @last_bundle_commit_ids ||=
    TYPES.inject({}) { |hsh, t| hsh.merge(t => {}) }

  if @last_bundle_commit_ids[type][bundle].blank?
    dir = self.assets_dir(type)
    filepaths = self.bundle_filepaths(bundle, type)
    if filepaths.present?
      @last_bundle_commit_ids[type][bundle] =
        self.last_commit_id(*filepaths)
    end
  end

  @last_bundle_commit_ids[type][bundle]
end

.last_commit_id(*args) ⇒ Object

Usage:

AssetHat.last_commit_id('public/stylesheets/application.css')
AssetHat.last_commit_id('public/stylesheets/ie.css',
                        'public/stylesheets/ie7.css',
                        'public/stylesheets/ie6.css')

Returns a string of the commit ID for the file with the most recent commit. If the file(s) cannot be found, ‘nil` is returned. Options:

vcs

Version control system. Currently, the only supported value is :git.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/asset_hat/vcs.rb', line 18

def self.last_commit_id(*args)
  # Process arguments
  options = args.extract_options!
  options = options.symbolize_keys.reverse_merge(:vcs => :git)
  filepaths = args.join(' ')

  # Validate options
  if options[:vcs] != :git
    raise 'Git is currently the only supported VCS.' and return
  end

  @last_commit_ids ||= {}
  if @last_commit_ids[filepaths].blank?
    h = `git log -1 --pretty=format:%h #{filepaths} 2>/dev/null`
      # `h` has either the abbreviated Git commit hash or an empty string
    @last_commit_ids[filepaths] = h if h.present?
  end
  @last_commit_ids[filepaths]
end

.min_filepath(filepath, extension) ⇒ Object

Returns the expected path for the minified version of an asset:

AssetHat.min_filepath('public/stylesheets/bundles/application.css', 'css')
  # => 'public/stylesheets/bundles/application.min.css'

See also AssetHat::CSS.min_filepath and AssetHat::JS.min_filepath.



200
201
202
# File 'lib/asset_hat.rb', line 200

def self.min_filepath(filepath, extension)
  filepath.sub(/([^\.]*).#{extension}$/, "\\1.min.#{extension}")
end

.ssl_asset_host_differs?Boolean

Returns true if the asset host differs between SSL and non-SSL pages, or false if the asset host doesn’t change.

Returns:

  • (Boolean)


288
289
290
291
292
# File 'lib/asset_hat.rb', line 288

def self.ssl_asset_host_differs?
  asset_host = ActionController::Base.asset_host
  AssetHat.compute_asset_host(asset_host, 'x.png') !=
    AssetHat.compute_asset_host(asset_host, 'x.png', :ssl => true)
end

.versionObject

Returns this gem’s version number. See also VERSION.



3
4
5
6
7
8
# File 'lib/asset_hat/version.rb', line 3

def self.version
  data_filepath = File.join(File.dirname(__FILE__), %w[.. .. VERSION.yml])
  data = YAML.load(File.open(data_filepath, 'r'))
  [:major, :minor, :patch, :build].
    map { |x| data[x] }.reject(&:blank?).join('.')
end