action-hero 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 33c72992fb39f78faad1dfd830fdb08727163293
4
+ data.tar.gz: deab7549b395659d3517c827be52c74b542f60e6
5
+ SHA512:
6
+ metadata.gz: 58d2b25fffb2ecad60810d066b69ca362d5b8ccd189baabe5773ce17bba06dea9fd10c2888d73c0bc47e9772772834bf4c3e97bbb261e4d4ba8edfe6d402483b
7
+ data.tar.gz: fe08fc4bc67513626538a34c699d9abdf755ab7ec249dfb88836d82096fdff78e354f6f368fd2f6d673e0bbaba6643b9464757384458dd2f99a4baf0736a70fb
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in action-hero.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jason Harrelson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,162 @@
1
+ # ActionHero
2
+
3
+ Move actions from methods in Rails controllers to action classes.
4
+
5
+
6
+ ## Motiviation
7
+
8
+ Simple Rails controllers implementing all of the REST actions get crowded enough with at least seven methods. Add any
9
+ helper methods, etc and things get much worse. In addition, the spec file is even more crowded. All of these concerns
10
+ jammed into a single class provide many opportunities for bugs. Why should you risk breaking your already implemented and
11
+ tested index action just because you are adding a show action?
12
+
13
+ Finally, controller specs are painfully slow. When ActionHero is used to implement your actions as classes you can achieve
14
+ full coverage without writing a single controller spec.
15
+
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ gem 'action-hero'
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install action-hero
30
+
31
+
32
+ ## Usage
33
+
34
+ ### Simplest Case
35
+
36
+ Include the ActionHero::Controller module in a controller.
37
+
38
+ # app/controllers/things_controller.rb
39
+ class ThingsController < ApplicationController
40
+ include ActionHero::Controller
41
+ end
42
+
43
+ Define the action class.
44
+
45
+ # app/actions/things/index.rb
46
+ module Things
47
+ class Index
48
+ include ActionHero::Action
49
+
50
+ def call
51
+ expose :things, Thing.all
52
+ respond_with @things
53
+ end
54
+
55
+ end
56
+ end
57
+
58
+ Use the exposed object in the view.
59
+
60
+ <%= things.each do |thing| %>
61
+ ...
62
+ <% end %>
63
+
64
+ ### Exposure
65
+
66
+ Unlike controller instance variables, the instance variables set in the action class will not automatically be available
67
+ in the view. This is just as well, as blindly making all instance variables in the controller available to the view is
68
+ arguably a bad idea. In order to solve this problem, ActionHero provides the #expose method.
69
+
70
+ The expose method does the following:
71
+
72
+ * Sets an instance variable in the action class with the same name as provided in the first argument to expose
73
+ * Returns the value passed in the second parameter so that you can set a local variable
74
+ * Stores the value in a data store held on the controller
75
+ * Implements a helper method on the controller to access the exposed value
76
+ * Makes the helper method available in the view
77
+
78
+ Example of usage:
79
+
80
+ # in the action class
81
+ count = expose( :count, 1 )
82
+ count # => 1
83
+ @count # => 1
84
+
85
+ #in the controller
86
+ count # => 1
87
+ @count # => nil
88
+
89
+ #in the view
90
+ count # => 1
91
+ @count # => nil
92
+
93
+
94
+ ### Implicit Controller
95
+
96
+ Because controller actions can now be defined outside of the controller, defining the controller is optional. If you
97
+ do not expcitily define a controller, ActionHero will fall back to the ImplicitController.
98
+
99
+ Ensure an implicit controller is defined. Without configuration, the implicit controller defaults to ImplicitController.
100
+
101
+ # app/controllers/implicit_controller.rb
102
+ class ImplicitController < ApplicationController
103
+ include ActionHero::Controller
104
+ end
105
+
106
+
107
+ ### Define Several Implicit Controllers and Configure Usage
108
+
109
+ In the case you want several implicit controllers due to different use cases, such as web app controller vs API you can
110
+ define several implicit controllers and configure the usage.
111
+
112
+ Define the implicit controllers.
113
+
114
+ # app/controllers/implicit_controller.rb
115
+ class ImplicitController < ApplicationController
116
+ include ActionHero::Controller
117
+ end
118
+
119
+ # app/controllers/api/v1/implicit_controller.rb
120
+ class Api::V1::ImplicitController < ApplicationController
121
+ include ActionHero::Controller
122
+ end
123
+
124
+ Configure the usage. The configuration uses regexs to match against the #controller_name and will use the first one
125
+ matched, so order matters.
126
+
127
+ # config/initializers/action_hero.rb
128
+ ActionHero.configure do |config|
129
+ config.implicit_controllers = [
130
+ [/^\/api\/.*/, Api::ImplicitController],
131
+ [/^\/.*/, ImplicitController]
132
+ ]
133
+ end
134
+
135
+ ### Mix and Match
136
+
137
+ You can mix and match usage of ActionHero action classes and standard Rails controller action methods.
138
+
139
+ In order to unobtrusively tie in to the controller, ActionHero::Controller implements the #action_missing
140
+ method. Thus, if you implement both an aciton class and an action method, the action method will win.
141
+
142
+ Define a controller with a show action method and define an action class for the index action.
143
+
144
+ # app/controllers/things_controller.rb
145
+ class ThingsController < ApplicationController
146
+ include ActionHero::Controller
147
+
148
+ def show
149
+ ...
150
+ end
151
+ end
152
+
153
+ # app/actions/things/index.rb
154
+ module Things
155
+ class Index
156
+ include ActionHero::action
157
+
158
+ def call
159
+ ...
160
+ end
161
+ end
162
+ end
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'action_hero/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "action-hero"
8
+ spec.version = ActionHero::VERSION
9
+ spec.authors = ["C. Jason Harrelson"]
10
+ spec.email = ["jason@lookforwardenterprises.com"]
11
+ spec.description = %q{Move actions from methods in Rails controllers to action classes.}
12
+ spec.summary = %q{Move actions from methods in Rails controllers to action classes.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency "rails"
25
+ end
@@ -0,0 +1 @@
1
+ require "action_hero"
@@ -0,0 +1,25 @@
1
+ require "action_hero/version"
2
+ require "dispatcher_ext"
3
+
4
+ module ActionHero
5
+
6
+ class ActionNotFound < StandardError #:nodoc:
7
+ end
8
+
9
+ autoload :Action, 'action_hero/action'
10
+ autoload :ActionResolver, 'action_hero/action_resolver'
11
+ autoload :Configuration, 'action_hero/configuration'
12
+ autoload :Controller, 'action_hero/controller'
13
+ autoload :LogSubscriber, 'action_hero/log_subscriber'
14
+
15
+ def self.configuration
16
+ @configuration ||= Configuration.new
17
+ end
18
+
19
+ def self.configure
20
+ yield( configuration ) if block_given?
21
+ end
22
+
23
+ end
24
+
25
+ ActionHero::LogSubscriber.attach_to :action_controller
@@ -0,0 +1,50 @@
1
+ require 'active_support/concern'
2
+ require 'forwardable'
3
+
4
+ module ActionHero
5
+ module Action
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+
11
+ extend Forwardable
12
+
13
+ attr_reader :controller
14
+ protected :controller
15
+
16
+ def_delegators :controller, :action_name,
17
+ :env,
18
+ :flash,
19
+ :formats,
20
+ :head,
21
+ :headers,
22
+ :params,
23
+ :redirect_to,
24
+ :render,
25
+ :render_to_string,
26
+ :request,
27
+ :reset_session,
28
+ :respond_with,
29
+ :response,
30
+ :session,
31
+ :url_for,
32
+ :url_options
33
+
34
+ end
35
+
36
+ def initialize( controller )
37
+ @controller = controller
38
+ end
39
+
40
+ protected
41
+
42
+ def expose( *args )
43
+ name = args.first.is_a?( Symbol ) ? args.first : (raise NotImplementedError)
44
+ value = args.last
45
+ instance_variable_set "@#{name}", value
46
+ controller.send :expose, *args
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ module ActionHero
2
+ class ActionResolver
3
+
4
+ attr_reader :module_name,
5
+ :action_name
6
+
7
+ def initialize( options )
8
+ @module_name = normalize_controller( options )
9
+ @action_name = options[:action_name]
10
+ end
11
+
12
+ def action_class
13
+ @action_class ||= action_class_name.constantize
14
+ end
15
+
16
+ def action_class_name
17
+ @action_class_name ||= [
18
+ module_name,
19
+ action_name.camelize
20
+ ].join( '::' )
21
+ end
22
+
23
+ def action_class_file_exists?
24
+ File.exists?( Rails.root.join( 'app', 'actions', "#{action_class_name.underscore}.rb" ))
25
+ end
26
+
27
+ protected
28
+
29
+ def normalize_controller( options )
30
+ if options[:controller_const]
31
+ options[:controller_const].gsub( /Controller$/, '' )
32
+ elsif options[:controller]
33
+ options[:controller].classify
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ module ActionHero
2
+
3
+ class Configuration
4
+
5
+ def implicit_controllers
6
+ @implicit_controllers ||= []
7
+ end
8
+
9
+ def implicit_controllers=( value )
10
+ @implicit_controllers = value
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,68 @@
1
+ require 'active_support/concern'
2
+
3
+ module ActionHero
4
+ module Controller
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ protected
9
+
10
+ def expose( *args )
11
+ name = args.first.is_a?( Symbol ) ? args.first : (raise NotImplementedError)
12
+ value = args.last
13
+ data.merge!( name => value )
14
+ self.class.send( :define_method, name, lambda { data[name] } )
15
+ self.class.helper_method name
16
+ value
17
+ end
18
+
19
+ def action_missing( name, *args, &block )
20
+ begin
21
+ action_class
22
+ rescue NoMethodError
23
+ # protect against misidentification due to
24
+ # NoMethodError being descendent of NameError
25
+ raise
26
+ rescue NameError => ex
27
+ raise ActionNotFound,
28
+ "The action #{action_class_name} nor #{self.class.name}##{name} could be found",
29
+ ex.backtrace
30
+ end
31
+ prepend_view_path "#{Rails.root}/app/views/#{params[:controller]}"
32
+ execute_action
33
+ end
34
+
35
+ # override _normalize_render in order to insert implied controller's view prefix
36
+ def _normalize_render(*args, &block)
37
+ options = _normalize_args(*args, &block)
38
+ _normalize_options(options)
39
+ unless options[:prefixes].include?( params[:controller] )
40
+ options[:prefixes].insert( 0, params[:controller] )
41
+ end
42
+ options
43
+ end
44
+
45
+ def data
46
+ @data ||= {}.with_indifferent_access
47
+ end
48
+
49
+ def execute_action
50
+ action = action_class.new( self )
51
+ action.call
52
+ end
53
+
54
+ def resolver
55
+ @resolver ||= ActionResolver.new( :controller_const => "#{params[:controller].classify}Controller",
56
+ :action_name => action_name )
57
+ end
58
+
59
+ def action_class
60
+ resolver.action_class
61
+ end
62
+
63
+ def action_class_name
64
+ resolver.action_class_name
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,12 @@
1
+ module ActionHero
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+
4
+ def start_processing( event )
5
+ controller_name = "#{event.payload[:params]['controller'].camelize}Controller"
6
+ unless event.payload[:controller] == controller_name
7
+ info "[ActionHero] No explicit #{controller_name} falling back to #{event.payload[:controller]}"
8
+ end
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ module ActionHero
2
+
3
+ VERSION = "0.1.0"
4
+
5
+ end
@@ -0,0 +1,47 @@
1
+ module ActionDispatch
2
+ module Routing
3
+ class RouteSet
4
+ class Dispatcher
5
+
6
+ private
7
+
8
+ def controller(params, default_controller=true)
9
+ if params && params.key?(:controller)
10
+ controller_param = params[:controller]
11
+ action_param = params[:action]
12
+ controller_reference(controller_param, action_param)
13
+ end
14
+ rescue NameError => e
15
+ raise ActionController::RoutingError, e.message, e.backtrace if default_controller
16
+ end
17
+
18
+ def controller_reference(controller_param, action_param)
19
+ const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
20
+ ActiveSupport::Dependencies.constantize(const_name)
21
+ rescue NoMethodError
22
+ # protect against misidentification due to
23
+ # NoMethodError being descendent of NameError
24
+ raise
25
+ rescue NameError
26
+ action_hero_resolver = ActionHero::ActionResolver.new( :controller => controller_param,
27
+ :action_name => action_param )
28
+ unless action_hero_resolver.action_class_file_exists?
29
+ raise
30
+ end
31
+
32
+ controller_const = nil
33
+ ActionHero.configuration.implicit_controllers.each do |regex, controller|
34
+ if regex.match( "/#{controller_param}" )
35
+ controller_const = controller
36
+ break
37
+ end
38
+ end
39
+
40
+ raise NameError unless controller_const
41
+ controller_const
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: action-hero
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - C. Jason Harrelson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Move actions from methods in Rails controllers to action classes.
56
+ email:
57
+ - jason@lookforwardenterprises.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - action-hero.gemspec
68
+ - lib/action-hero.rb
69
+ - lib/action_hero.rb
70
+ - lib/action_hero/action.rb
71
+ - lib/action_hero/action_resolver.rb
72
+ - lib/action_hero/configuration.rb
73
+ - lib/action_hero/controller.rb
74
+ - lib/action_hero/log_subscriber.rb
75
+ - lib/action_hero/version.rb
76
+ - lib/dispatcher_ext.rb
77
+ homepage: ''
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.0.5
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Move actions from methods in Rails controllers to action classes.
101
+ test_files: []