Permalink
Browse files

integration with focused controller

  • Loading branch information...
1 parent de6b372 commit 881e6315622e3248893a4aecff53600cfbfb15eb @kristianmandrup committed Aug 1, 2012
View
@@ -3,3 +3,4 @@
Gemfile.lock
pkg/*
.idea/*
+.DS_Store
View
@@ -0,0 +1,99 @@
+## Controllers, Actions and Commands
+
+An analysis of the current MVC design pattern in Rails, in particular how the Controllers
+are designed and can be improved by employing different Design Patterns to ensure better
+decoupling, single responsibility and how to aovid the typical Rails anti-pattern of flat classes, bloated with methods.
+
+## Using Imperator Commands in the current Controller pattern
+
+Demonstrates just how ugly the current Controller convention is!!!
+
+```ruby
+class PostController < ApplicationController
+ def update
+ update_command.valid? ? update_valid : update_invalid
+ end
+
+ protected
+
+ def update_valid
+ update_command.perform and redirect_to(root_url)
+ end
+
+ def update_invalid
+ render edit_post_path(command.object)
+ end
+
+ def update_command
+ @update_command ||= UpdatePostCommand.new(params[:post])
+ end
+end
+```
+
+## Using Imperator Commands with Focused Controllers
+
+Much nicer encapsulated logic using FocusedController :)
+
+```ruby
+module PostController
+ class Update < UpdateCommandAction
+ invalid_path do
+ root_url
+ end
+
+ # generated by naming convention
+ # command { @command ||= UpdatePostCommand.new post_hash }
+ end
+end
+```
+
+Demonstrating some customization of Controller logic
+
+```ruby
+module PostController
+ class Update < UpdateCommandAction
+ valid do
+ command.perform
+ redirect_to root_url
+ end
+
+ def invalid
+ flash_msg "#{command.object} was invalid!", :error
+ super
+ end
+
+ command { @command ||= command_class.new object_hash.merge(:status => :complete) }
+ end
+end
+```
+
+And more...
+
+```ruby
+module PostController
+ class Update < UpdateCommandAction
+ valid do
+ flash_msg "#{command.object} was valid!"
+ command.perform
+ puts "#{command} was performed!"
+ valid_redirect
+ end
+
+ valid_redirect_path do
+ root_url
+ end
+
+ def invalid
+ flash_msg "#{command.object} was invalid!", :error
+ super
+ end
+
+ command do
+ @command ||= begin
+ c = UpdatePostCommand.new post_hash
+ c.complete_it!
+ end
+ end
+ end
+end
+```
View
@@ -2,6 +2,8 @@
require 'imperator/invalid_command_error'
require 'imperator/command'
+require 'imperator/focused' if defined?(FocusedController)
+
module Imperator
# Your code goes here...
end
View
@@ -17,13 +17,20 @@ class Imperator::Command
define_model_callbacks :create, :perform, :initialize
attribute :id, String, :default => proc { UUID.generate }
+ attribute :object, Object
def self.action(&block)
define_method(:action, &block)
end
alias_method :params, :attributes
+ def to_s
+ str = "Command: #{id}"
+ str << " - #{object}" if object
+ str
+ end
+
def as_json(*args)
attributes.as_json(*args)
end
View
@@ -0,0 +1,2 @@
+require 'imperator/focused/command_action'
+require 'imperator/focused/rest_actions'
@@ -0,0 +1,3 @@
+class FocusedAction < ApplicationController
+ include FocusedController::Mixin
+end
@@ -0,0 +1,84 @@
+class Imperator
+ module Focused
+ class CommandAction < FocusedAction
+ def run
+ command.valid? ? perform_valid : perform_invalid
+ end
+
+ def valid
+ command.perform
+ valid_redirect
+ end
+
+ def invalid
+ invalid_render
+ end
+
+ def resource_class_name
+ self.class.resource_class_name
+ end
+
+ def resource_class
+ self.class.resource_class
+ end
+
+ protected
+
+ def valid_redirect
+ redirect_to valid_redirect_path
+ end
+
+ def invalid_render
+ render send(invalid_path, command.object)
+ end
+
+ def invalid_path
+ raise NotImplementedError, "Must be implemented by subclass"
+ end
+
+ def valid_redirect_path
+ raise NotImplementedError, "Must be implemented by subclass"
+ end
+
+ def action
+ self.class.name.underscore
+ end
+
+ def flash_msg msg, type = :notice
+ flash[type.to_sym] = msg
+ end
+
+ def command
+ @command ||= default_command
+ end
+
+ def default_command
+ command_class.new object_hash
+ end
+
+ def object_hash
+ send("#{resource_class_name}_hash")
+ end
+
+ def command_class
+ @command_class ||= "#{action.to_s.camelize}#{resource_class_name}Command".constantize
+ end
+ end
+ end
+end
+
+# create convenience constant
+CommandAction = Imperator::Focused::CommandAction
+
+require 'imperator/focused/command_action/class_methods'
+
+class Imperator
+ module Focused::CommandAction
+ extend ActiveSupport::Concern
+
+ # generates fx #post_hash method for PostController
+ define_method "#{resource_class_name}_hash" do
+ params[resource_class.to_sym]
+ end
+ end
+end
@@ -0,0 +1,84 @@
+class Imperator
+ module Focused::CommandAction
+ module ClassMethods
+ def run &block
+ define_method :run do
+ yield block
+ end
+ end
+
+ def action &block
+ define_method :action do
+ yield block
+ end
+ end
+
+ def valid &block
+ define_method :valid do
+ yield block
+ end
+ end
+
+ def invalid &block
+ define_method :valid do
+ yield block
+ end
+ end
+
+ def valid_redirect_path &block
+ define_method :valid_redirect_path do
+ yield block
+ end
+ end
+
+ def invalid_path &block
+ define_method :invalid_path do
+ yield block
+ end
+ end
+
+ attr_writer :resource_class
+
+ def resource_class_name
+ resource_class.to_s.underscore
+ end
+
+ # extracted from https://github.com/josevalim/inherited_resources/blob/master/lib/inherited_resources/class_methods.rb
+ def resource_class
+ # First priority is the namespaced model, e.g. User::Group
+ self.resource_class ||= begin
+ namespaced_class = self.name.sub(/Controller/, '').singularize
+ namespaced_class.constantize
+ rescue NameError
+ nil
+ end
+
+ # Second priority is the top namespace model, e.g. EngineName::Article for EngineName::Admin::ArticlesController
+ self.resource_class ||= begin
+ namespaced_classes = self.name.sub(/Controller/, '').split('::')
+ namespaced_class = [namespaced_classes.first, namespaced_classes.last].join('::').singularize
+ namespaced_class.constantize
+ rescue NameError
+ nil
+ end
+
+ # Third priority the camelcased c, i.e. UserGroup
+ self.resource_class ||= begin
+ camelcased_class = self.name.sub(/Controller/, '').gsub('::', '').singularize
+ camelcased_class.constantize
+ rescue NameError
+ nil
+ end
+
+ # Otherwise use the Group class, or fail
+ self.resource_class ||= begin
+ class_name = self.controller_name.classify
+ class_name.constantize
+ rescue NameError => e
+ raise unless e.message.include?(class_name)
+ nil
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,19 @@
+require 'imperator/focused/command_action'
+
+class Imperator
+ module Focused
+ class CreateCommandAction < CommandAction
+
+ protected
+
+ delegate :invalid_path, :to => :new_path
+
+ def new_path
+ "new_#{resource_class_name}_path"
+ end
+ end
+ end
+end
+
+# create convenience constant
+CreateCommandAction = Imperator::Focused::CreateCommandAction
@@ -0,0 +1,19 @@
+require 'imperator/focused/command_action'
+
+class Imperator
+ module Focused
+ class DeleteCommandAction < CommandAction
+
+ protected
+
+ delegate :invalid_path, :to => :index_path
+
+ def index_path
+ "#{resource_class_name.pluralize}_path"
+ end
+ end
+ end
+end
+
+# create convenience constant
+DeleteCommandAction = Imperator::Focused::DeleteCommandAction
@@ -0,0 +1,3 @@
+[:create, :update, :delete].each do |action|
+ require "imperator/focused/#{action}_command_action"
+end
@@ -0,0 +1,19 @@
+require 'imperator/focused/command_action'
+
+class Imperator
+ module Focused
+ class UpdateCommandAction < CommandAction
+
+ protected
+
+ delegate :invalid_path, :to => :edit_path
+
+ def edit_path
+ "edit_#{resource_class_name}_path"
+ end
+ end
+ end
+end
+
+# create convenience constant
+UpdateCommandAction = Imperator::Focused::UpdateCommandAction
View
@@ -1,3 +1,3 @@
module Imperator
- VERSION = "0.2.0"
+ VERSION = "0.3.0"
end
@@ -0,0 +1,11 @@
+require 'imperator'
+
+describe CommandAction do
+ describe "default functionality" do
+ pending 'TODO'
+ end
+
+ describe "override default functionality" do
+ pending 'TODO'
+ end
+end
Oops, something went wrong.

0 comments on commit 881e631

Please sign in to comment.