tension 0.3 → 0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 067f889277f3fb790d4fb64e6b196754f2ca9b92
4
+ data.tar.gz: ac1afed7e475d2e99cc72c330d224c1611951f13
5
+ SHA512:
6
+ metadata.gz: 2124f18b0a5c85ac50891d6ac74d6fa3f528afbc08dbee97177b66553ef357e8a05c8a1ad5b57f2154cd1eb0585be6ce33529c7da8660ad9c8b742d8bd9393d7
7
+ data.tar.gz: 107bb478074ab4b6c502217b29a5b21db0a44ebb22cdd422887c9261788db40e5178a6a8ee7111390a65a63bc983a3a5e3282f53a10959ce98ad71155ed69ce0
data/README.md CHANGED
@@ -3,4 +3,40 @@ Tension
3
3
  _Tighten up Rails's asset pipeline for CSS & JS._
4
4
 
5
5
  Rails's asset pipeline is smart and well–implemented under the hood, but it can
6
- be a challenge to organize your CSS and JavaScript so that
6
+ be a challenge to organize your CSS and JavaScript so that you don't have to
7
+ struggle to maintain which files get compiled, where they live, and how they're
8
+ included in views and templates.
9
+
10
+ _Tension_ helps you out. It takes Rails's existing controller/view file structure
11
+ and applies it to JavaScript and CSS assets as well. Let's take a sample Rails app:
12
+
13
+ app
14
+ + assets
15
+ + controllers
16
+ + account
17
+ + api
18
+ + blog
19
+ + posts_controller.rb
20
+ + models
21
+ + views
22
+ + account
23
+ + api
24
+ + blog
25
+ + posts
26
+ + index.html.erb
27
+ + layouts
28
+ + blog.html.erb
29
+
30
+ The standard structure Rails enforces is __module__ → __controller__ →
31
+ __action__. For the PostsController, the __action__ logic is tucked away alongside
32
+ other actions in `posts_controller.rb`, but its view is a separate file namespaced
33
+ under the _blog_ module and _posts_ controller.
34
+
35
+ This is a logical, intuitive structure. Our assets should follow it too.
36
+
37
+ Using Rails's asset pipeline you can drop an asset anywhere within the `assets`
38
+ directory and Rails will compile and serve it, but Rails enforces no logical
39
+ structure on your files. This leads to messy, hard-to-maintain code, and it gets
40
+ worse when you precompile assets in production.
41
+
42
+ ...
@@ -0,0 +1,167 @@
1
+ module Tension
2
+
3
+ # Tension::Environment exposes `asset_paths`, which describes the assets that
4
+ # can be automatically included in templates.
5
+ #
6
+ # It's used automatically on application load (see Tension::Railtie) to populate
7
+ # the asset pipeline's list of assets to precompile, and caches the result in-
8
+ # process for use by Tension::Tagger when templates are rendered.
9
+ #
10
+ class Environment
11
+
12
+ class << self
13
+
14
+ GLOBAL_KEY = "::globals".freeze
15
+ COMMON_SUFFIX = "_common".freeze
16
+ ASSET_TYPES = [{ container: "javascripts", extension: "js" },
17
+ { container: "stylesheets", extension: "css" }].freeze
18
+
19
+ # This method collects all asset paths for the asset pipeline to
20
+ # precompile. It determines which additional assets to include in the
21
+ # pipeline's precompilation process, so they can be automatically added
22
+ # to templates later (see Tension::Tagger).
23
+ #
24
+ # If an application's routes include `resources :people`, `asset_map`
25
+ # will attempt to locate the following assets:
26
+ #
27
+ # + people_common.{js,css}
28
+ # + people/index.{js,css}
29
+ # + people/show.{js,css}
30
+ # + people/new.{js,css}
31
+ # + people/edit.{js,css}
32
+ #
33
+ # Note that Tension ignores Sprockets' interpretation of `index.{js,css}`
34
+ # as a shared file, and therefore requires the `_common` suffix for
35
+ # controller-wide assets.
36
+ #
37
+ # Returns: an Hash of controller paths, each containing that controller's
38
+ # routed GET actions mapped to each action's best matching asset. E.g.
39
+ #
40
+ # { "people" => {
41
+ # "index" => {
42
+ # "js" => "javascripts/people/index.js",
43
+ # "css" => "javascripts/people_common.css"
44
+ # },
45
+ # "show" => {
46
+ # "js" => "javascripts/application.js",
47
+ # "css" => "javascripts/people_common.css"
48
+ # }
49
+ # }
50
+ #
51
+ def asset_map
52
+ if @asset_map.nil?
53
+ @asset_map = Hash.new
54
+ globals = Hash.new
55
+
56
+ ASSET_TYPES.each do |type|
57
+ # Find and store the global asset for this type.
58
+ global_asset = valid_asset( "application.#{ type[:extension] }" )
59
+ globals.store( type[:extension], global_asset )
60
+ end
61
+
62
+ # TODO: add support for looking up the tree...
63
+ search_paths.each_pair do |controller_path, actions|
64
+ next unless local_controller?( controller_path )
65
+
66
+ @asset_map.store( controller_path, Hash.new )
67
+
68
+ ASSET_TYPES.each do |type|
69
+ # Attempt to locate a common asset for this controller.
70
+ common_path = "#{ controller_path }#{ COMMON_SUFFIX }.#{ type[:extension] }"
71
+ common_asset = valid_asset( common_path ) || globals.fetch( type[:extension] )
72
+
73
+ actions.each do |action|
74
+ if @asset_map[ controller_path ][ action ].nil?
75
+ @asset_map.fetch( controller_path )
76
+ .store( action, Hash.new )
77
+ end
78
+
79
+ action_asset = valid_asset( "#{ [ controller_path, action ].join("/") }.#{ type[:extension] }" )
80
+ @asset_map.fetch( controller_path )
81
+ .fetch( action )
82
+ .store( type[:extension], action_asset || common_asset )
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+
89
+ @asset_map
90
+ end
91
+
92
+ # All unique, existing asset paths.
93
+ def asset_paths
94
+ @asset_paths ||= Set.new.merge( extract_paths(asset_map) ).to_a
95
+ end
96
+
97
+
98
+ private
99
+
100
+ # Recursively extracts all paths from the `asset_map`.
101
+ def extract_paths hash
102
+ paths = Array.new
103
+
104
+ hash.each_pair do |key, value|
105
+ if value.is_a?(Hash)
106
+ paths.concat( extract_paths(value) )
107
+ else
108
+ paths << value.pathname.to_s
109
+ end
110
+ end
111
+
112
+ paths
113
+ end
114
+
115
+ # A hash of controller paths mapped to action names. These controller/action
116
+ # pairs correspond to configured routes for which assets may be required.
117
+ #
118
+ # e.g. { "blog" => [ "index", "show" ],
119
+ # "admin/blog" => [ "index", "show", "edit" ] }
120
+ #
121
+ def search_paths
122
+ @search_paths ||= configured_route_defaults.reduce( Hash.new ) do |accum, route_default|
123
+ accum[ route_default[:controller] ] ||= Array.new
124
+ accum[ route_default[:controller] ].push( route_default[:action] )
125
+
126
+ accum
127
+ end
128
+ end
129
+
130
+ # Routing defaults (including controller path and action name) for all
131
+ # configured GET routes.
132
+ #
133
+ def configured_route_defaults
134
+ if @configured_route_defaults.nil?
135
+ get_routes = Rails.application.routes.routes.find_all do |route|
136
+ route.verb.match("GET")
137
+ end
138
+
139
+ @configured_route_defaults = get_routes.map do |route|
140
+ route.defaults unless route.defaults.empty?
141
+ end
142
+
143
+ @configured_route_defaults.compact!
144
+ end
145
+
146
+ @configured_route_defaults
147
+ end
148
+
149
+ # Returns: a real BundledAsset present in the Sprockets index, or nil
150
+ # if no asset was found.
151
+ #
152
+ def valid_asset asset_path
153
+ Rails.application.assets.find_asset( asset_path )
154
+ end
155
+
156
+ # Returns: true if the controller targeted in the routes is present in this
157
+ # app's local code rather than in a gem. Gems are responsible for ensuring
158
+ # availability of their own assets.
159
+ #
160
+ def local_controller? controller_path
161
+ File.exists?("#{ Rails.root }/app/controllers/#{ controller_path }_controller.rb")
162
+ end
163
+
164
+ end
165
+
166
+ end
167
+ end
@@ -4,14 +4,19 @@ module Tension
4
4
  require "rails"
5
5
 
6
6
  class Railtie < Rails::Railtie
7
+ initializer "tension.add_assets_to_precompile_list" do |app|
8
+ ActiveSupport.on_load :after_initialize do
7
9
 
8
- initializer "tension.asset_pipeline" do |app|
9
- ActiveSupport.on_load :rails do
10
- if !Rails.env.development? && !Rails.env.test?
11
- Rails.application.config.assets.precompile += Tension::Collector.collect_assets
10
+ Rails.application.reload_routes!
11
+ Tension.load_assets!
12
+
13
+ ApplicationHelper.send(:include, Tension::TensionHelper)
14
+
15
+ Rails.application.config.assets.precompile << lambda do |path, filename|
16
+ Tension::Environment.asset_paths.include?( filename )
12
17
  end
18
+
13
19
  end
14
20
  end
15
-
16
21
  end
17
22
  end
@@ -0,0 +1,26 @@
1
+ module Tension
2
+
3
+ # Tagger is included in ActionView::Helpers so it can be called from
4
+ # templates and layouts.
5
+ module TensionHelper
6
+ extend ActiveSupport::Concern
7
+
8
+ # Just call
9
+ def asset_for type, request = request
10
+ asset = Tension::Environment.asset_map
11
+ .fetch( request.params[:controller] )
12
+ .fetch( request.params[:action] )
13
+ .fetch( type.to_s )
14
+
15
+ include_method = case type
16
+ when :js
17
+ :javascript_include_tag
18
+ when :css
19
+ :stylesheet_link_tag
20
+ end
21
+
22
+ send( include_method, asset.logical_path )
23
+ end
24
+ end
25
+
26
+ end
data/lib/tension.rb CHANGED
@@ -0,0 +1,21 @@
1
+ require 'active_support/ordered_options'
2
+
3
+ require 'tension/environment'
4
+ require 'tension/railtie' if defined?(Rails)
5
+ require 'tension/tension_helper'
6
+
7
+ module Tension
8
+ def self.load_assets!
9
+ !!Tension::Environment.asset_map
10
+ end
11
+
12
+ # def self.config
13
+ # @config ||= begin
14
+ # config = ActiveSupport::OrderedOptions.new
15
+
16
+ # config.enabled = !Rails.env.development? && !Rails.env.test?
17
+
18
+ # config
19
+ # end
20
+ # end
21
+ end
data/tension.gemspec CHANGED
@@ -1,11 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "tension"
3
3
 
4
- s.version = "0.3"
5
- s.date = "2013-02-22"
4
+ s.version = "0.9"
5
+ s.date = "2013-03-21"
6
+ s.license = "MIT"
6
7
 
7
8
  s.summary = "Tighten up Rails's asset pipeline for CSS & JS."
8
- s.description = "Tension brings some sanity to CSS & JS organization for modern front–end development."
9
+ s.description = "Tension brings some sanity to Rails's CSS & JS organization for modern front–end development."
9
10
 
10
11
  s.authors = [ "Piers Mainwaring" ]
11
12
  s.email = "piers@impossibly.org"
@@ -13,6 +14,5 @@ Gem::Specification.new do |s|
13
14
  s.homepage = "https://github.com/piersadrian/tension"
14
15
  s.require_paths = [ "lib" ]
15
16
 
16
- s.add_dependency "activerecord", "~> 3.2.0"
17
- s.add_dependency "activesupport", "~> 3.2.0"
17
+ s.add_dependency "rails", ">= 3.2"
18
18
  end
metadata CHANGED
@@ -1,50 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tension
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.3'
5
- prerelease:
4
+ version: '0.9'
6
5
  platform: ruby
7
6
  authors:
8
7
  - Piers Mainwaring
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-02-22 00:00:00.000000000 Z
11
+ date: 2013-03-21 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- name: activerecord
16
- prerelease: false
14
+ name: rails
17
15
  requirement: !ruby/object:Gem::Requirement
18
16
  requirements:
19
- - - ~>
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
- version: 3.2.0
22
- none: false
19
+ version: '3.2'
23
20
  type: :runtime
24
- version_requirements: !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ~>
27
- - !ruby/object:Gem::Version
28
- version: 3.2.0
29
- none: false
30
- - !ruby/object:Gem::Dependency
31
- name: activesupport
32
21
  prerelease: false
33
- requirement: !ruby/object:Gem::Requirement
34
- requirements:
35
- - - ~>
36
- - !ruby/object:Gem::Version
37
- version: 3.2.0
38
- none: false
39
- type: :runtime
40
22
  version_requirements: !ruby/object:Gem::Requirement
41
23
  requirements:
42
- - - ~>
24
+ - - '>='
43
25
  - !ruby/object:Gem::Version
44
- version: 3.2.0
45
- none: false
46
- description: Tension brings some sanity to CSS & JS organization for modern front–end
47
- development.
26
+ version: '3.2'
27
+ description: Tension brings some sanity to Rails's CSS & JS organization for modern
28
+ front–end development.
48
29
  email: piers@impossibly.org
49
30
  executables: []
50
31
  extensions: []
@@ -53,32 +34,32 @@ files:
53
34
  - LICENSE
54
35
  - README.md
55
36
  - lib/tension.rb
56
- - lib/tension/application.rb
37
+ - lib/tension/environment.rb
57
38
  - lib/tension/railtie.rb
58
- - lib/tension/tagger.rb
39
+ - lib/tension/tension_helper.rb
59
40
  - tension.gemspec
60
41
  homepage: https://github.com/piersadrian/tension
61
- licenses: []
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
62
45
  post_install_message:
63
46
  rdoc_options: []
64
47
  require_paths:
65
48
  - lib
66
49
  required_ruby_version: !ruby/object:Gem::Requirement
67
50
  requirements:
68
- - - ! '>='
51
+ - - '>='
69
52
  - !ruby/object:Gem::Version
70
53
  version: '0'
71
- none: false
72
54
  required_rubygems_version: !ruby/object:Gem::Requirement
73
55
  requirements:
74
- - - ! '>='
56
+ - - '>='
75
57
  - !ruby/object:Gem::Version
76
58
  version: '0'
77
- none: false
78
59
  requirements: []
79
60
  rubyforge_project:
80
- rubygems_version: 1.8.24
61
+ rubygems_version: 2.0.2
81
62
  signing_key:
82
- specification_version: 3
63
+ specification_version: 4
83
64
  summary: Tighten up Rails's asset pipeline for CSS & JS.
84
65
  test_files: []
@@ -1,62 +0,0 @@
1
- module Tension
2
- class Application
3
-
4
- class << self
5
-
6
- # This method collects all asset paths for the asset pipeline to
7
- # precompile. It's called from `config/application.rb` when determining
8
- # which additional assets to include in the pipeline's precompilation
9
- # process. Tension will assume that any subdirectories in your assets
10
- # directory are module scopes. You can also explicitly set module scopes:
11
- #
12
- # Rails.application.config.tension_modules = %W( blog account )
13
- #
14
- # Tension::Application will then search for all javascripts and stylesheets
15
- # one filesystem level deep in those scopes. The search paths become:
16
- #
17
- # app/assets/{javascripts,stylesheets}/*.{js,css}
18
- # app/assets/{javascripts,stylesheets}/{blog,account}/**/*.{js,css}
19
- #
20
- # Any assets in these paths will be added to the pipeline and compiled.
21
-
22
- def collect_assets
23
- assets = %W(stylesheets javascripts).map do |type|
24
- glob_within_asset_path( type ) + module_scopes.map do |scope|
25
- glob_within_asset_path( type, scope )
26
- end
27
- end
28
-
29
- assets.flatten
30
- end
31
-
32
-
33
- private
34
-
35
- def module_scopes
36
- # find dirs in app/assets
37
- end
38
-
39
- # Loads the file paths within a given subdirectory of "app/assets/".
40
- def glob_within_asset_path type, *path_parts
41
- # Only recursively find children if a particular subdirectory of an
42
- # asset `type` is given. That way we can specify WHICH subdirectories
43
- # of a `type` have assets that matter.
44
- path_parts << "**" if path_parts.present?
45
-
46
- root_path = File.join(Rails.root, "app", "assets", type)
47
- pattern = File.expand_path( File.join(*path_parts, "*.*"), root_path )
48
-
49
- paths = Dir.glob( pattern ).map do |file_path|
50
- # Remove extra, pre-compilation file extensions and any part of the
51
- # filepath at or above the asset `type`.
52
- file_path.gsub( file_path.match(/(\.css|\.js)(.*)/)[2], '' )
53
- .gsub( root_path + "/", '' ) rescue nil
54
- end
55
-
56
- paths.compact
57
- end
58
-
59
- end
60
-
61
- end
62
- end
@@ -1,82 +0,0 @@
1
- module Pipeline
2
- class Builder
3
-
4
- GLOBAL_ASSET_NAME = "application".freeze
5
- SHARED_SUFFIX = "_common".freeze
6
- ASSET_SPECIFICITY_ORDER = [ :action, :controller, :module ].freeze
7
-
8
- # Determines which JS/CSS files should be included based on the current
9
- # controller, action, and which files actually exist. If the current
10
- # controller is "OrcaHealth::PagesController" and action is "products",
11
- # this method (called with `type = :js`) will attempt to include
12
- #
13
- # + orca_health/pages/products.js
14
- # + orca_health/pages_common.js
15
- # + orca_health.js
16
- #
17
- # in that order. ONLY THE FIRST ASSET LOCATED WILL BE INCLUDED. Hence,
18
- # you should always use Sprockets directives to include dependencies.
19
- #
20
- # Any files that don't exist will not be included in the page. Pass
21
- # `{ except: :application }` to exclude `application.{js,css}`.
22
-
23
- def self.asset_paths type, controller_path, action_name, options = {}
24
- options[:except] = [ options[:except] ] unless options[:except].is_a? Array
25
-
26
- # Check if the best asset's already been loaded and stored.
27
- asset = known_asset_paths[ controller_path ].try(:fetch, action_name, nil).try(:fetch, type, nil)
28
-
29
- if asset.nil?
30
- path_components = controller_path.split("/")
31
- controller_name = path_components.pop
32
- module_path = path_components.join("/")
33
-
34
- possible_paths = {
35
- module: module_path,
36
- controller: [ module_path, controller_name + SHARED_SUFFIX ].join("/"),
37
- action: [ module_path, controller_name, action_name ].join("/")
38
- }
39
-
40
- # Find and store the best possible asset for this controller/action combo.
41
- asset = most_specific_asset_path( type, possible_paths )
42
-
43
- known_asset_paths[ controller_path ] ||= Hash.new
44
- known_asset_paths[ controller_path ][ action_name ] ||= Hash.new
45
- known_asset_paths[ controller_path ][ action_name ][ type ] = asset
46
- end
47
-
48
- assets = if options[:except].include? :application
49
- [ asset ]
50
- else
51
- [ GLOBAL_ASSET_NAME, asset ]
52
- end
53
-
54
- assets.compact
55
- end
56
-
57
-
58
- private
59
-
60
- def self.known_asset_paths
61
- @known_asset_paths ||= Hash.new
62
- end
63
-
64
- def self.most_specific_asset_path filetype, paths
65
- ASSET_SPECIFICITY_ORDER.each do |name|
66
- path = paths[name]
67
-
68
- # Using the Sprockets `#index` helps performance in production.
69
- asset = if Rails.env.production?
70
- Rails.application.assets.index.find_asset([ path, filetype ].join("."))
71
- else
72
- Rails.application.assets.find_asset([ path, filetype ].join("."))
73
- end
74
-
75
- # This loop is relatively expensive, so return the asset path as soon
76
- # as possible rather than continue looping.
77
- break path if asset.present?
78
- end
79
- end
80
-
81
- end
82
- end