komando 0.0.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,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