komando 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,25 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+ doc
21
+
22
+ ## PROJECT::SPECIFIC
23
+ *.rbc
24
+ .bundle
25
+ Gemfile.lock
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.2@komando
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+ gemspec
3
+
4
+ # vim: set ft=ruby
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 François Beausoleil
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,147 @@
1
+ Komando
2
+ =======
3
+
4
+ Commands are used in many applications, especially GUIs. Komando is an implementation of the [Command pattern][1],
5
+ especially suited to Rails applications. Commands are provided the information they need, then told to run. Commands
6
+ can be marked best effort, or mandatory. Commands can refer to other commands, and ask that the sub-commands be
7
+ mandatory or best effort.
8
+
9
+ Most web applications have a lot of before and after hooks that occur when working with objects: sending a
10
+ welcome email on registration, incrementing or decrementing counter caches, trigger validation on remote web
11
+ services. When implemented using callbacks, all these occur without the developer knowing about them. A
12
+ simple change in one area of the code can have a huge impact somewhere else. Inspiration for this came from
13
+ [Unintented Consequences: The Pitfalls of ActiveRecord Callbacks][2] and
14
+ [Crazy, Heretical, and Awesome: The Way I Write Rails Apps][3].
15
+
16
+
17
+ Examples
18
+ --------
19
+
20
+ require "komando/command"
21
+ require "komando/active_record"
22
+
23
+ class AdUpdateCommand
24
+ include Komando::Command
25
+
26
+ def initialize(*args)
27
+ @initiated_at = Time.now.utc
28
+
29
+ # If you must override #initialize, NEVER forget to call super
30
+ super
31
+
32
+ # Forgetting to call super will result in NoMethodError and such
33
+ # being raised from your code.
34
+ end
35
+
36
+ # Lets exceptions through -- nothing is rescued -- callers will have
37
+ # to handle exceptions themselves. The command's success/failure state
38
+ # is determined by the fact that no exceptions are raised. The block's
39
+ # return value is ignored.
40
+ #
41
+ # All #mandatory_steps are run within a single database transaction.
42
+ mandatory_steps do
43
+ @ad.update_attributes!(@params)
44
+ @ad.campaign.update_attribute(:active_ad_units_count, @ad.campaign.ad_units.active.count)
45
+ end
46
+
47
+ # The #transaction block can be used to root your transaction differently.
48
+ # The default #transaction block simply yields - no transactions will be
49
+ # processed. The komando-active_record gem will root your transactions
50
+ # against ActiveRecord::Base#transaction.
51
+ #
52
+ # This method is important if you have more than one database connection,
53
+ # where each model might open transactions against different databases.
54
+ transaction do
55
+ AdUnit.transaction do
56
+ yield
57
+ end
58
+ end
59
+ end
60
+
61
+ class PlacementEventLoggerCommand
62
+ include Komando::Command
63
+
64
+ mandatory_steps do
65
+ Event.create!(:event_type => "placement:created", :actor => @actor, :subject => @subject)
66
+ end
67
+ end
68
+
69
+ class PlacementCreationCommand
70
+ include Komando::Command
71
+
72
+ mandatory_steps do
73
+ @placement = Placement.create!(@params)
74
+
75
+ @placement.campaign.increment!(:active_placements_count, 1) if @placement.active?
76
+ @placement.campaign.increment!(:scheduled_placements_count, 1) if @placement.scheduled?
77
+ end
78
+
79
+ # Call #best_effort_step multiple times to declare each individual step
80
+ # that will be attempted. If a block fails, logging will ensue, and
81
+ # other blocks will be attempted.
82
+ #
83
+ # #best_effort_step can document what it's supposed to do, enabling
84
+ # better logging. Either pass a String or Symbol, the latter of which will
85
+ # be #humanized.
86
+ best_effort_step(:event_generation) do
87
+
88
+ # Note the availability of a class-level method named #run, which simply does the obvious
89
+ # instantiation and call the instance-level #run.
90
+ PlacementEventLoggerCommand.run(:actor => @placement.created_by, :subject => @placement)
91
+
92
+ end
93
+ end
94
+
95
+ # Usage from Rails
96
+ class AdsController < ApplicationController
97
+
98
+ def update
99
+ @ad = Ad.find(params[:id])
100
+
101
+ # Commands accept any number of parameters, as a Hash. Parameters are translated
102
+ # to instance variables within the Command object itself.
103
+ command = AdUpdateCommand.new(:ad => @ad,
104
+ :params => params[:ad])
105
+ if command.run then
106
+ flash[:notice] = "Ad updated"
107
+ redirect_to @ad
108
+ else
109
+ flash.new[:error] = "Ad failed to save"
110
+ render :action => :edit
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+
117
+ Notes on Patches/Pull Requests
118
+ -----------------------------
119
+
120
+ * Fork the project.
121
+ * Make your feature addition or bug fix.
122
+ * Add tests for it. This is important so I don't break it in a
123
+ future version unintentionally.
124
+ * Commit, do not mess with rakefile, version, or history.
125
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
126
+ * Send me a pull request. Bonus points for topic branches.
127
+
128
+
129
+ Compatibility
130
+ -------------
131
+
132
+ Komando is known to pass it's specifications on the following Ruby implementations (rvm version specifiers):
133
+
134
+ * jruby-1.5.6 [ x86_64-java ]
135
+ * ree-1.8.7-2011.01 [ x86_64 ]
136
+ * ruby-1.8.7-p330 [ x86_64 ]
137
+ * ruby-1.9.2-p136 [ x86_64 ]
138
+
139
+
140
+ Copyright
141
+ ---------
142
+
143
+ Copyright (c) 2010 François Beausoleil. See LICENSE for details.
144
+
145
+ [1]: http://en.wikipedia.org/wiki/Command_pattern
146
+ [2]: http://blog.teksol.info/2010/09/28/unintented-consequences-the-pitfalls-of-activerecord-callbacks.html
147
+ [3]: http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html
@@ -0,0 +1,111 @@
1
+ # coding: utf-8
2
+
3
+ require 'rake'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "komando"
9
+ gem.summary = %Q{Command-driven framework, especially suited for web applications}
10
+ gem.description = %Q{Most web applications have a lot of before/after hooks that occur when working with objects: sending a welcome email on registration, incrementing/decrementing counter caches, trigger validation on remote web services. When implemented using callbacks, all these occur without the developer knowing about them. A simple change in one area of the code can have a huge impact somewhere else. Inspiration for this came from http://blog.teksol.info/2010/09/28/unintented-consequences-the-pitfalls-of-activerecord-callbacks.html and http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html}
11
+ gem.email = "francois@teksol.info"
12
+ gem.homepage = "http://github.com/francois/komando"
13
+ gem.authors = ["François Beausoleil"]
14
+
15
+ # Don't bundle development code with the gem
16
+ gem.files -= FileList["samples/**/*"]
17
+
18
+ gem.add_development_dependency "bacon", ">= 0"
19
+ gem.add_development_dependency "yard", ">= 0"
20
+ gem.add_development_dependency "bluecloth", ">= 0"
21
+ # BlueCloth is "required" by yard, where it is used to format the docs, but is not really a required dependency.
22
+ # Use 1.9.2 to generate the docs.
23
+
24
+ gem.add_development_dependency "activerecord", "~> 2.3.8"
25
+ gem.add_development_dependency "jeweler", "~> 1.4.0"
26
+
27
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
28
+ end
29
+ Jeweler::GemcutterTasks.new
30
+ rescue LoadError
31
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
32
+ end
33
+
34
+ require 'rake/testtask'
35
+ Rake::TestTask.new(:spec) do |spec|
36
+ spec.libs << 'lib' << 'spec'
37
+ spec.pattern = 'spec/**/*_spec.rb'
38
+ spec.verbose = true
39
+ end
40
+
41
+ begin
42
+ require 'rcov/rcovtask'
43
+ Rcov::RcovTask.new do |spec|
44
+ spec.libs << 'spec'
45
+ spec.pattern = 'spec/**/*_spec.rb'
46
+ spec.verbose = true
47
+ end
48
+ rescue LoadError
49
+ task :rcov do
50
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
51
+ end
52
+ end
53
+
54
+ task :spec => :check_dependencies
55
+
56
+ task :default => :spec
57
+
58
+ begin
59
+ require 'yard'
60
+ YARD::Rake::YardocTask.new
61
+ rescue LoadError
62
+ task :yardoc do
63
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
64
+ end
65
+ end
66
+
67
+ RUBIES = %w(
68
+ 1.9.2@komando
69
+ 1.8.7@komando
70
+ ree@komando
71
+ jruby@komando
72
+ )
73
+
74
+ def rvm(command)
75
+ sh "rvm #{RUBIES.join(",")} exec #{command}"
76
+ end
77
+
78
+ namespace :rubies do
79
+ namespace :bundle do
80
+ task :install do
81
+ rvm "gem install bundler"
82
+ rvm "bundle install"
83
+ end
84
+ end
85
+
86
+ task :version do
87
+ rvm "ruby --version"
88
+ end
89
+
90
+ task :spec do
91
+ rvm "rake spec"
92
+ end
93
+
94
+ task :default do
95
+ sh "rvm use #{RUBIES.first}"
96
+ end
97
+
98
+ namespace :gemset do
99
+ task :create do
100
+ RUBIES.each do |ruby|
101
+ sh "rvm use #{ruby} && rvm --force gemset create komando"
102
+ end
103
+ end
104
+
105
+ task :delete do
106
+ RUBIES.each do |ruby|
107
+ sh "rvm use #{ruby} && rvm --force gemset delete"
108
+ end
109
+ end
110
+ end
111
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,82 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{komando}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["François Beausoleil"]
12
+ s.date = %q{2011-03-15}
13
+ s.description = %q{Most web applications have a lot of before/after hooks that occur when working with objects: sending a welcome email on registration, incrementing/decrementing counter caches, trigger validation on remote web services. When implemented using callbacks, all these occur without the developer knowing about them. A simple change in one area of the code can have a huge impact somewhere else. Inspiration for this came from http://blog.teksol.info/2010/09/28/unintented-consequences-the-pitfalls-of-activerecord-callbacks.html and http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html}
14
+ s.email = %q{francois@teksol.info}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ ".rvmrc",
23
+ "Gemfile",
24
+ "LICENSE",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "komando.gemspec",
29
+ "lib/komando.rb",
30
+ "lib/komando/command.rb",
31
+ "lib/komando/command/dsl.rb",
32
+ "lib/komando/persistence.rb",
33
+ "lib/komando/persistence/active_record.rb",
34
+ "lib/komando/version.rb",
35
+ "samples/rails3/.gitignore",
36
+ "samples/rails3/.rvmrc",
37
+ "samples/rails3/lib/tasks/.gitkeep",
38
+ "samples/rails3/public/stylesheets/.gitkeep",
39
+ "samples/rails3/vendor/plugins/.gitkeep",
40
+ "spec/active_record_integration_spec.rb",
41
+ "spec/command_runner_spec.rb",
42
+ "spec/dsl_spec.rb",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/francois/komando}
46
+ s.rdoc_options = ["--charset=UTF-8"]
47
+ s.require_paths = ["lib"]
48
+ s.rubygems_version = %q{1.3.7}
49
+ s.summary = %q{Command-driven framework, especially suited for web applications}
50
+ s.test_files = [
51
+ "spec/active_record_integration_spec.rb",
52
+ "spec/command_runner_spec.rb",
53
+ "spec/dsl_spec.rb",
54
+ "spec/spec_helper.rb"
55
+ ]
56
+
57
+ if s.respond_to? :specification_version then
58
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
59
+ s.specification_version = 3
60
+
61
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
62
+ s.add_development_dependency(%q<bacon>, [">= 0"])
63
+ s.add_development_dependency(%q<yard>, [">= 0"])
64
+ s.add_development_dependency(%q<bluecloth>, [">= 0"])
65
+ s.add_development_dependency(%q<activerecord>, ["~> 2.3.8"])
66
+ s.add_development_dependency(%q<jeweler>, ["~> 1.4.0"])
67
+ else
68
+ s.add_dependency(%q<bacon>, [">= 0"])
69
+ s.add_dependency(%q<yard>, [">= 0"])
70
+ s.add_dependency(%q<bluecloth>, [">= 0"])
71
+ s.add_dependency(%q<activerecord>, ["~> 2.3.8"])
72
+ s.add_dependency(%q<jeweler>, ["~> 1.4.0"])
73
+ end
74
+ else
75
+ s.add_dependency(%q<bacon>, [">= 0"])
76
+ s.add_dependency(%q<yard>, [">= 0"])
77
+ s.add_dependency(%q<bluecloth>, [">= 0"])
78
+ s.add_dependency(%q<activerecord>, ["~> 2.3.8"])
79
+ s.add_dependency(%q<jeweler>, ["~> 1.4.0"])
80
+ end
81
+ end
82
+
@@ -0,0 +1,18 @@
1
+ # The main Komando module. Holder for declarations.
2
+ module Komando
3
+
4
+ # Indicates the mandatory steps have not been declared on instances of this class.
5
+ class MissingMandatoryStepsError < StandardError; end
6
+
7
+ autoload :Command, "komando/command"
8
+
9
+ def self.make_command(base)
10
+ base.send :include, Komando::Command
11
+ base.send :extend, Komando::Command::Dsl
12
+ if defined?(ActiveRecord) then
13
+ require "komando/persistence/active_record"
14
+ base.send :include, Komando::Persistence::ActiveRecord
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,76 @@
1
+ module Komando
2
+ module Command
3
+
4
+ autoload :Dsl, "komando/command/dsl"
5
+
6
+ # Instantiates and runs this command.
7
+ #
8
+ # @param (see #initialize)
9
+ # @return [true] When the mandatory parts of the command are run to completion.
10
+ def self.run!(instance_variable_declarations)
11
+ new.run!(instance_variable_declarations)
12
+ end
13
+
14
+ # @param [Hash] instance_variable_declarations The list of instance variables to declare on this instance.
15
+ # The instance variables will be available in the blocks, courtesy of Ruby
16
+ # and it's dynamic nature.
17
+ def initialize(instance_variable_declarations={})
18
+ instance_variable_declarations.each do |name, value|
19
+ instance_variable_set("@#{name}", value)
20
+ end
21
+ end
22
+
23
+ # Executes the mandatory and best effort steps, in order of definition, raising or
24
+ # logging and swallowing exceptions as appropriate.
25
+ #
26
+ # @return [true] Always returns +true+, since exceptions indicate failure.
27
+ # @raise [Exception] In case of a failure in the mandatory part of the command.
28
+ def run!
29
+ run_mandatory!
30
+ run_best_effort
31
+ true
32
+ end
33
+
34
+ # Returns a Logger-like object that will log receive #warn, #info and #debug calls.
35
+ # The logger can do whatever it wants with those calls.
36
+ #
37
+ # @return [#warn, #info, #debug] A Logger-like object that responds to logging commands.
38
+ #
39
+ # @example
40
+ #
41
+ # # config/initializers/komando.rb
42
+ # require "komando/command"
43
+ #
44
+ # module Komando::Command
45
+ # def logger
46
+ # Rails.logger
47
+ # end
48
+ # end
49
+ def logger
50
+ @logger ||= Logger.new(STDERR)
51
+ end
52
+
53
+ private
54
+
55
+ # Runs and raises
56
+ def run_mandatory!
57
+ steps = self.class.mandatory_steps
58
+ raise Komando::MissingMandatoryStepsError if steps.empty?
59
+ steps.each do |step|
60
+ instance_exec(&step)
61
+ end
62
+ end
63
+
64
+ # Runs and logs
65
+ def run_best_effort
66
+ self.class.best_effort_steps.each do |name, block|
67
+ begin
68
+ instance_exec &block
69
+ rescue StandardError => e
70
+ logger.warn "[Komando] Ignoring failed #{name.inspect} step in #{self.class}: #{e.class.name} - #{e.message}"
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,95 @@
1
+ module Komando
2
+ module Command
3
+
4
+ # The Komando DSL. Extend your command classes with this module to start using
5
+ # Komando in your application.
6
+ #
7
+ # It is recommended you do not implement #initialize in your commands. If you do,
8
+ # you *must* call super or your parameters will not be available as instance variables.
9
+ #
10
+ # @example
11
+ #
12
+ # require "komando/command/dsl"
13
+ #
14
+ # class CreateUserCommand
15
+ # extend Komando::Command::Dsl
16
+ #
17
+ # # If you must override #initialize, make sure you call super,
18
+ # # or your instance variables won't be assigned.
19
+ # def initialize(*args)
20
+ # super # MUST call, or all hell will break loose
21
+ # end
22
+ #
23
+ # mandatory_step "generate records" do
24
+ # # TODO
25
+ # end
26
+ #
27
+ # mandatory_step "generate audit log" do
28
+ # # TODO
29
+ # end
30
+ #
31
+ # end
32
+ module Dsl
33
+
34
+ # Returns the list of mandatory steps
35
+ def mandatory_steps
36
+ @mandatory_steps ||= []
37
+ end
38
+
39
+ # Declares a set of actions that must run to completion for this command to be deemed successful.
40
+ # The declared actions may be anything: method calls or direct actions. Parameters are passed from the
41
+ # environment as instance variables passed to the instance.
42
+ #
43
+ # @example
44
+ #
45
+ # class CreateUserCommand
46
+ # extend Komando::Command::Dsl
47
+ #
48
+ # mandatory_step do
49
+ # # Assuming an ActiveRecord-like User class exists
50
+ # User.create!(@attributes)
51
+ # end
52
+ # end
53
+ #
54
+ # # Run the command with parameters gathered from the environment
55
+ # CreateUserCommand.new(:attributes => params[:user]).run!
56
+ def mandatory_step(name=nil, &block)
57
+ mandatory_steps << block
58
+ end
59
+
60
+ # Declares a new best effort step - one that will be executed, but will not stop processing.
61
+ # If the block raises an exception, {Komando::Command#run!} will log and swallow the exception.
62
+ # Best effort stop blocks have access to the same environment as {#mandatory_step} blocks -
63
+ # they execute within the same instance. You can pass values from one block to the next by
64
+ # using instance variables.
65
+ #
66
+ # @example
67
+ #
68
+ # class CreateUserCommand
69
+ # extend Komando::Command::Dsl
70
+ #
71
+ # mandatory_step do
72
+ # @user = User.create!(@attributes)
73
+ # end
74
+ #
75
+ # best_effort_step("generate audit log record") do
76
+ # AuditLog.append(@user, @created_by)
77
+ # end
78
+ # end
79
+ #
80
+ # # Run the command with parameters gathered from the environment
81
+ # CreateUserCommand.new(:attributes => params[:user], :created_by => current_user).run!
82
+ def best_effort_step(name=nil, &block)
83
+ @best_effort_steps ||= Array.new
84
+ @best_effort_steps << [name, block]
85
+ end
86
+
87
+ # Helper method to access the declared blocks.
88
+ #
89
+ # @return [Array] The list of best effort blocks that were collected. This array may be empty.
90
+ def best_effort_steps
91
+ @best_effort_steps ||= Array.new
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,6 @@
1
+ module Komando
2
+ # Persistence helpers that wrap {Komando::Command} executions within
3
+ # transactions, or other mechanisms that ease use with persistence.
4
+ module Persistence
5
+ end
6
+ end
@@ -0,0 +1,65 @@
1
+ require "komando"
2
+ require "active_record"
3
+
4
+ module Komando
5
+ module Persistence
6
+ # Wraps command executions within a database transaction.
7
+ module ActiveRecord
8
+
9
+ # Insinuates this module within {Komando::Command}.
10
+ #
11
+ # @private
12
+ def self.included(base)
13
+ base.send :alias_method_chain, :run!, :transaction
14
+ end
15
+
16
+ # Wraps a command execution within a database transactions.
17
+ # This method delegates actual transaction semantics to {#wrap_transaction}.
18
+ # This method is renamed as {Komando::Command##run!} during inclusion.
19
+ def run_with_transaction!
20
+ wrap_transaction do
21
+ run_without_transaction!
22
+ end
23
+ end
24
+
25
+ # Does the actual work of wrapping in a transaction. The default is to wrap
26
+ # using {ActiveRecord::Base#transaction}.
27
+ #
28
+ # @example Wrapping using a specific connection
29
+ # # config/database.yml
30
+ # development:
31
+ # adapter: sqlite3
32
+ # database: db/development.sqlite3
33
+ #
34
+ # require "komando/persistence/active_record"
35
+ #
36
+ # class User < ActiveRecord::Base
37
+ # establish_connection :adapter => "sqlite3", :database => "/var/dbs/users.sqlite3"
38
+ # end
39
+ #
40
+ # class CreateUserCommand
41
+ # include Komando::Command
42
+ # include Komando::Persistence::ActiveRecord
43
+ #
44
+ # def wrap_transaction
45
+ # User.transaction do
46
+ # yield
47
+ # end
48
+ # end
49
+ # end
50
+ #
51
+ # @yieldreturn The block's last value.
52
+ def wrap_transaction
53
+ ::ActiveRecord::Base.transaction do
54
+ yield
55
+ end
56
+ end
57
+
58
+ # Uses the same Logger instance as ActiveRecord.
59
+ def logger
60
+ ::ActiveRecord::Base.logger
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ module Komando
2
+ module Version
3
+
4
+ MAJOR = "0"
5
+ MINOR = "0"
6
+ PATCH = "0"
7
+
8
+ def self.to_s
9
+ [MAJOR, MINOR, PATCH].join(".")
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ .bundle
2
+ db/*.sqlite3
3
+ log/*.log
4
+ tmp/**/*
@@ -0,0 +1 @@
1
+ rvm 1.9.2@komando-rails3
File without changes
File without changes
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+ require "komando/persistence/active_record"
3
+
4
+ describe "An active-record enabled command" do
5
+
6
+ before do
7
+ ActiveRecord::Base.establish_connection :adapter => adapter_name, :database => ":memory:"
8
+ end
9
+
10
+ def adapter_name
11
+ return "jdbcsqlite3" if defined?(JRUBY_VERSION)
12
+ return "sqlite3"
13
+ end
14
+
15
+ before do
16
+ @command = Class.new do
17
+ extend Komando::Command::Dsl
18
+ include Komando::Command
19
+ include Komando::Persistence::ActiveRecord
20
+
21
+ attr_reader :open_transactions
22
+
23
+ mandatory_step do
24
+ @open_transactions = ActiveRecord::Base.connection.open_transactions
25
+ end
26
+ end
27
+ end
28
+
29
+ should "wrap the mandatory_steps within an ActiveRecord transaction" do
30
+ command = @command.new
31
+ command.run!
32
+ command.open_transactions.should == 1
33
+ end
34
+
35
+ end
@@ -0,0 +1,242 @@
1
+ require "spec_helper"
2
+
3
+ describe "A command with a mandatory step" do
4
+
5
+ before do
6
+ @command = Class.new do
7
+ extend Komando::Command::Dsl
8
+ include Komando::Command
9
+
10
+ attr_reader :ran
11
+
12
+ mandatory_step do
13
+ @ran = true
14
+ end
15
+ end
16
+ end
17
+
18
+ should "run the mandatory steps" do
19
+ command = @command.new
20
+ command.run!
21
+
22
+ command.ran.should == true
23
+ end
24
+
25
+ end
26
+
27
+ describe "A command with a failing mandatory step" do
28
+
29
+ before do
30
+ @command = Class.new do
31
+ extend Komando::Command::Dsl
32
+ include Komando::Command
33
+
34
+ attr_reader :ran, :log
35
+
36
+ mandatory_step do
37
+ raise "failure to run"
38
+ end
39
+
40
+ best_effort_step do
41
+ @log ||= []
42
+ @log << 1
43
+ end
44
+ end
45
+ end
46
+
47
+ should "let the exception bubble through" do
48
+ lambda do
49
+ @command.new.run!
50
+ end.should.raise(RuntimeError)
51
+ end
52
+
53
+ should "NOT run best effort blocks" do
54
+ command = @command.new
55
+ begin
56
+ command.run!
57
+ rescue StandardError => ignored
58
+ # NOP
59
+ end
60
+
61
+ command.log.should.be.nil?
62
+ end
63
+
64
+ end
65
+
66
+ describe "A command with no step declarations" do
67
+
68
+ before do
69
+ @command = Class.new do
70
+ extend Komando::Command::Dsl
71
+ include Komando::Command
72
+ end
73
+ end
74
+
75
+ should "raise an exception when running" do
76
+ lambda { @command.new.run! }.should.raise(Komando::MissingMandatoryStepsError)
77
+ end
78
+
79
+ end
80
+
81
+ describe "A command with a best effort step" do
82
+
83
+ before do
84
+ @command = Class.new do
85
+ extend Komando::Command::Dsl
86
+ include Komando::Command
87
+
88
+ attr_reader :ran
89
+
90
+ mandatory_step do
91
+ # NOP
92
+ end
93
+
94
+ best_effort_step(:always_succeeds) do
95
+ @ran = true
96
+ end
97
+ end
98
+ end
99
+
100
+ should "run the best effort step" do
101
+ command = @command.new
102
+ command.run!
103
+ command.ran.should == true
104
+ end
105
+
106
+ end
107
+
108
+ describe "A command with two mandatory steps" do
109
+
110
+ before do
111
+ @command = Class.new do
112
+ extend Komando::Command::Dsl
113
+ include Komando::Command
114
+
115
+ attr_accessor :raise_in_first, :raise_in_second
116
+ attr_reader :log
117
+
118
+ def initialize(*args)
119
+ @log = []
120
+ super
121
+ end
122
+
123
+ mandatory_step do
124
+ raise "asked to raise" if raise_in_first
125
+ log << 1
126
+ end
127
+
128
+ mandatory_step do
129
+ raise "asked to raise" if raise_in_second
130
+ log << 2
131
+ end
132
+ end
133
+ end
134
+
135
+ should "run both blocks in order" do
136
+ command = @command.new
137
+ command.run!
138
+
139
+ command.log.should == [1, 2]
140
+ end
141
+
142
+ should "NOT run the 2nd block when the 1st one raises" do
143
+ command = @command.new
144
+ command.raise_in_first = true
145
+
146
+ lambda do
147
+ command.run!
148
+ end.should.raise
149
+
150
+ command.log.should == []
151
+ end
152
+
153
+ end
154
+
155
+ describe "A command with two best effort steps" do
156
+
157
+ before do
158
+ @command = Class.new do
159
+ extend Komando::Command::Dsl
160
+ include Komando::Command
161
+
162
+ attr_reader :log
163
+
164
+ mandatory_step do
165
+ # NOP
166
+ end
167
+
168
+ best_effort_step(:first) do
169
+ @log ||= []
170
+ @log << 1
171
+ end
172
+
173
+ best_effort_step(:second) do
174
+ @log ||= []
175
+ @log << 2
176
+ end
177
+ end
178
+ end
179
+
180
+ should "run both blocks, in order" do
181
+ command = @command.new
182
+ command.run!
183
+ command.log.should == [1, 2]
184
+ end
185
+
186
+ end
187
+
188
+ describe "A command with two best effort steps, where the 1st will fail" do
189
+
190
+ after do
191
+ $OK = false
192
+ end
193
+
194
+ before do
195
+ @command = Class.new do
196
+ extend Komando::Command::Dsl
197
+ include Komando::Command
198
+
199
+ attr_reader :log
200
+
201
+ mandatory_step do
202
+ # NOP
203
+ end
204
+
205
+ best_effort_step(:first) do
206
+ raise "failure to run"
207
+ end
208
+
209
+ best_effort_step(:second) do
210
+ @log ||= []
211
+ @log << 2
212
+ end
213
+
214
+ def logger
215
+ @logger ||= Class.new do
216
+ attr_reader :messages
217
+
218
+ def warn(message)
219
+ @messages ||= []
220
+ @messages << message
221
+ end
222
+ end.new
223
+ end
224
+ end
225
+ end
226
+
227
+ should "run both blocks, in order, and not return errors" do
228
+ command = @command.new
229
+ command.run!
230
+ command.log.should == [2]
231
+ end
232
+
233
+ should "log errors using #logger" do
234
+ command = @command.new
235
+ command.run!
236
+ messages = command.logger.messages
237
+
238
+ messages.length.should == 1
239
+ messages.first.should.match /ignoring failed.*first.*RuntimeError.*failure to run/im
240
+ end
241
+
242
+ end
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ describe "A command with no step declarations" do
4
+
5
+ before do
6
+ @command = Class.new do
7
+ extend Komando::Command::Dsl
8
+ end
9
+ end
10
+
11
+ should "have no mandatory step blocks" do
12
+ @command.mandatory_steps.should == []
13
+ end
14
+
15
+ should "have no best effort blocks" do
16
+ @command.best_effort_steps.should == []
17
+ end
18
+ end
19
+
20
+ describe "A command with one mandatory step block and no best effort blocks" do
21
+
22
+ before do
23
+ @command = Class.new do
24
+ extend Komando::Command::Dsl
25
+
26
+ mandatory_step do
27
+ end
28
+ end
29
+ end
30
+
31
+ should "have a mandatory step block" do
32
+ @command.mandatory_steps.should.not.be.nil?
33
+ end
34
+
35
+ should "allow declaring a second mandatory step" do
36
+ lambda do
37
+ @command.class_eval do
38
+ mandatory_step do
39
+ end
40
+ end
41
+ end.should.not.raise
42
+ end
43
+
44
+ end
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'bacon'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+
7
+ require 'komando'
8
+ require "komando/command"
9
+ require "komando/command/dsl"
10
+
11
+ print "\n\n#{RUBY_PLATFORM} -- #{RUBY_VERSION} -- #{RUBY_RELEASE_DATE}"
12
+ print "-- JRuby Detected" if Object.const_defined?("JRUBY_VERSION")
13
+ print "\n\n"
14
+
15
+ Bacon.summary_on_exit
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: komando
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 0
9
+ version: 0.0.0
10
+ platform: ruby
11
+ authors:
12
+ - "Fran\xC3\xA7ois Beausoleil"
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-15 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bacon
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: yard
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: bluecloth
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :development
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: activerecord
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 2
69
+ - 3
70
+ - 8
71
+ version: 2.3.8
72
+ type: :development
73
+ version_requirements: *id004
74
+ - !ruby/object:Gem::Dependency
75
+ name: jeweler
76
+ prerelease: false
77
+ requirement: &id005 !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ segments:
83
+ - 1
84
+ - 4
85
+ - 0
86
+ version: 1.4.0
87
+ type: :development
88
+ version_requirements: *id005
89
+ description: "Most web applications have a lot of before/after hooks that occur when working with objects: sending a welcome email on registration, incrementing/decrementing counter caches, trigger validation on remote web services. When implemented using callbacks, all these occur without the developer knowing about them. A simple change in one area of the code can have a huge impact somewhere else. Inspiration for this came from http://blog.teksol.info/2010/09/28/unintented-consequences-the-pitfalls-of-activerecord-callbacks.html and http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html"
90
+ email: francois@teksol.info
91
+ executables: []
92
+
93
+ extensions: []
94
+
95
+ extra_rdoc_files:
96
+ - LICENSE
97
+ - README.md
98
+ files:
99
+ - .document
100
+ - .gitignore
101
+ - .rvmrc
102
+ - Gemfile
103
+ - LICENSE
104
+ - README.md
105
+ - Rakefile
106
+ - VERSION
107
+ - komando.gemspec
108
+ - lib/komando.rb
109
+ - lib/komando/command.rb
110
+ - lib/komando/command/dsl.rb
111
+ - lib/komando/persistence.rb
112
+ - lib/komando/persistence/active_record.rb
113
+ - lib/komando/version.rb
114
+ - samples/rails3/.gitignore
115
+ - samples/rails3/.rvmrc
116
+ - samples/rails3/lib/tasks/.gitkeep
117
+ - samples/rails3/public/stylesheets/.gitkeep
118
+ - samples/rails3/vendor/plugins/.gitkeep
119
+ - spec/active_record_integration_spec.rb
120
+ - spec/command_runner_spec.rb
121
+ - spec/dsl_spec.rb
122
+ - spec/spec_helper.rb
123
+ has_rdoc: true
124
+ homepage: http://github.com/francois/komando
125
+ licenses: []
126
+
127
+ post_install_message:
128
+ rdoc_options:
129
+ - --charset=UTF-8
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ segments:
138
+ - 0
139
+ version: "0"
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ segments:
146
+ - 0
147
+ version: "0"
148
+ requirements: []
149
+
150
+ rubyforge_project:
151
+ rubygems_version: 1.3.7
152
+ signing_key:
153
+ specification_version: 3
154
+ summary: Command-driven framework, especially suited for web applications
155
+ test_files:
156
+ - spec/active_record_integration_spec.rb
157
+ - spec/command_runner_spec.rb
158
+ - spec/dsl_spec.rb
159
+ - spec/spec_helper.rb