action-hero 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []