command_model 1.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.
Files changed (72) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +4 -0
  4. data/Guardfile +9 -0
  5. data/LICENSE +22 -0
  6. data/README.md +99 -0
  7. data/Rakefile +2 -0
  8. data/command_model.gemspec +23 -0
  9. data/examples/bank/.gitignore +15 -0
  10. data/examples/bank/Gemfile +38 -0
  11. data/examples/bank/README.rdoc +261 -0
  12. data/examples/bank/Rakefile +7 -0
  13. data/examples/bank/app/assets/images/rails.png +0 -0
  14. data/examples/bank/app/assets/javascripts/accounts.js.coffee +3 -0
  15. data/examples/bank/app/assets/javascripts/application.js +15 -0
  16. data/examples/bank/app/assets/stylesheets/accounts.css.scss +3 -0
  17. data/examples/bank/app/assets/stylesheets/application.css +13 -0
  18. data/examples/bank/app/controllers/accounts_controller.rb +62 -0
  19. data/examples/bank/app/controllers/application_controller.rb +3 -0
  20. data/examples/bank/app/helpers/accounts_helper.rb +2 -0
  21. data/examples/bank/app/helpers/application_helper.rb +2 -0
  22. data/examples/bank/app/mailers/.gitkeep +0 -0
  23. data/examples/bank/app/models/.gitkeep +0 -0
  24. data/examples/bank/app/models/account.rb +65 -0
  25. data/examples/bank/app/views/accounts/deposit_form.html.erb +23 -0
  26. data/examples/bank/app/views/accounts/index.html.erb +22 -0
  27. data/examples/bank/app/views/accounts/transfer_form.html.erb +32 -0
  28. data/examples/bank/app/views/accounts/withdraw_form.html.erb +23 -0
  29. data/examples/bank/app/views/layouts/application.html.erb +15 -0
  30. data/examples/bank/config.ru +4 -0
  31. data/examples/bank/config/application.rb +65 -0
  32. data/examples/bank/config/boot.rb +6 -0
  33. data/examples/bank/config/environment.rb +5 -0
  34. data/examples/bank/config/environments/development.rb +31 -0
  35. data/examples/bank/config/environments/production.rb +64 -0
  36. data/examples/bank/config/environments/test.rb +35 -0
  37. data/examples/bank/config/initializers/accounts.rb +6 -0
  38. data/examples/bank/config/initializers/backtrace_silencers.rb +7 -0
  39. data/examples/bank/config/initializers/inflections.rb +15 -0
  40. data/examples/bank/config/initializers/mime_types.rb +5 -0
  41. data/examples/bank/config/initializers/secret_token.rb +7 -0
  42. data/examples/bank/config/initializers/session_store.rb +8 -0
  43. data/examples/bank/config/initializers/wrap_parameters.rb +10 -0
  44. data/examples/bank/config/locales/en.yml +5 -0
  45. data/examples/bank/config/routes.rb +12 -0
  46. data/examples/bank/db/seeds.rb +7 -0
  47. data/examples/bank/lib/assets/.gitkeep +0 -0
  48. data/examples/bank/lib/tasks/.gitkeep +0 -0
  49. data/examples/bank/log/.gitkeep +0 -0
  50. data/examples/bank/public/404.html +26 -0
  51. data/examples/bank/public/422.html +26 -0
  52. data/examples/bank/public/500.html +25 -0
  53. data/examples/bank/public/favicon.ico +0 -0
  54. data/examples/bank/public/robots.txt +5 -0
  55. data/examples/bank/script/rails +6 -0
  56. data/examples/bank/test/fixtures/.gitkeep +0 -0
  57. data/examples/bank/test/functional/.gitkeep +0 -0
  58. data/examples/bank/test/functional/accounts_controller_test.rb +19 -0
  59. data/examples/bank/test/integration/.gitkeep +0 -0
  60. data/examples/bank/test/performance/browsing_test.rb +12 -0
  61. data/examples/bank/test/test_helper.rb +7 -0
  62. data/examples/bank/test/unit/.gitkeep +0 -0
  63. data/examples/bank/test/unit/helpers/accounts_helper_test.rb +4 -0
  64. data/examples/bank/vendor/assets/javascripts/.gitkeep +0 -0
  65. data/examples/bank/vendor/assets/stylesheets/.gitkeep +0 -0
  66. data/examples/bank/vendor/plugins/.gitkeep +0 -0
  67. data/lib/command_model.rb +7 -0
  68. data/lib/command_model/model.rb +173 -0
  69. data/lib/command_model/version.rb +3 -0
  70. data/spec/model_spec.rb +222 -0
  71. data/spec/spec_helper.rb +2 -0
  72. metadata +164 -0
@@ -0,0 +1,35 @@
1
+ Bank::Application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb
3
+
4
+ # The test environment is used exclusively to run your application's
5
+ # test suite. You never need to work with it otherwise. Remember that
6
+ # your test database is "scratch space" for the test suite and is wiped
7
+ # and recreated between test runs. Don't rely on the data there!
8
+ config.cache_classes = true
9
+
10
+ # Configure static asset server for tests with Cache-Control for performance
11
+ config.serve_static_assets = true
12
+ config.static_cache_control = "public, max-age=3600"
13
+
14
+ # Log error messages when you accidentally call methods on nil
15
+ config.whiny_nils = true
16
+
17
+ # Show full error reports and disable caching
18
+ config.consider_all_requests_local = true
19
+ config.action_controller.perform_caching = false
20
+
21
+ # Raise exceptions instead of rendering exception templates
22
+ config.action_dispatch.show_exceptions = false
23
+
24
+ # Disable request forgery protection in test environment
25
+ config.action_controller.allow_forgery_protection = false
26
+
27
+ # Tell Action Mailer not to deliver emails to the real world.
28
+ # The :test delivery method accumulates sent emails in the
29
+ # ActionMailer::Base.deliveries array.
30
+ config.action_mailer.delivery_method = :test
31
+
32
+
33
+ # Print deprecation notices to the stderr
34
+ config.active_support.deprecation = :stderr
35
+ end
@@ -0,0 +1,6 @@
1
+ ACCOUNTS = [
2
+ Account.new("Matthew", 1000),
3
+ Account.new("Mark", 500),
4
+ Account.new("Luke", 800),
5
+ Account.new("John", 200)
6
+ ]
@@ -0,0 +1,7 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4
+ # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5
+
6
+ # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7
+ # Rails.backtrace_cleaner.remove_silencers!
@@ -0,0 +1,15 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new inflection rules using the following format
4
+ # (all these examples are active by default):
5
+ # ActiveSupport::Inflector.inflections do |inflect|
6
+ # inflect.plural /^(ox)$/i, '\1en'
7
+ # inflect.singular /^(ox)en/i, '\1'
8
+ # inflect.irregular 'person', 'people'
9
+ # inflect.uncountable %w( fish sheep )
10
+ # end
11
+ #
12
+ # These inflection rules are supported but not enabled by default:
13
+ # ActiveSupport::Inflector.inflections do |inflect|
14
+ # inflect.acronym 'RESTful'
15
+ # end
@@ -0,0 +1,5 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new mime types for use in respond_to blocks:
4
+ # Mime::Type.register "text/richtext", :rtf
5
+ # Mime::Type.register_alias "text/html", :iphone
@@ -0,0 +1,7 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Your secret key for verifying the integrity of signed cookies.
4
+ # If you change this key, all old signed cookies will become invalid!
5
+ # Make sure the secret is at least 30 characters and all random,
6
+ # no regular words or you'll be exposed to dictionary attacks.
7
+ Bank::Application.config.secret_token = '85ecca5acd1ebe24d4dde79f727ecdf024e614fe2178c8fafc86037e43028479cd34ac819c045e43af2822e3dcd613658ab6f3580c3593e74e7d933a442797a3'
@@ -0,0 +1,8 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ Bank::Application.config.session_store :cookie_store, key: '_bank_session'
4
+
5
+ # Use the database for sessions instead of the cookie-based default,
6
+ # which shouldn't be used to store highly confidential information
7
+ # (create the session table with "rails generate session_migration")
8
+ # Bank::Application.config.session_store :active_record_store
@@ -0,0 +1,10 @@
1
+ # Be sure to restart your server when you modify this file.
2
+ #
3
+ # This file contains settings for ActionController::ParamsWrapper which
4
+ # is enabled by default.
5
+
6
+ # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7
+ ActiveSupport.on_load(:action_controller) do
8
+ wrap_parameters format: [:json]
9
+ end
10
+
@@ -0,0 +1,5 @@
1
+ # Sample localization file for English. Add more files in this directory for other locales.
2
+ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3
+
4
+ en:
5
+ hello: "Hello world"
@@ -0,0 +1,12 @@
1
+ Bank::Application.routes.draw do
2
+ get "accounts/:id/withdraw" => "accounts#withdraw_form", :as => :account_withdraw_form
3
+ post "accounts/:id/withdraw" => "accounts#withdraw", :as => :account_withdraw
4
+
5
+ get "accounts/:id/deposit" => "accounts#deposit_form", :as => :account_deposit_form
6
+ post "accounts/:id/deposit" => "accounts#deposit", :as => :account_deposit
7
+
8
+ get "accounts/transfer" => "accounts#transfer_form", :as => :account_transfer_form
9
+ post "accounts/transfer" => "accounts#transfer", :as => :account_transfer
10
+
11
+ root :to => "accounts#index"
12
+ end
@@ -0,0 +1,7 @@
1
+ # This file should contain all the record creation needed to seed the database with its default values.
2
+ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3
+ #
4
+ # Examples:
5
+ #
6
+ # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7
+ # Mayor.create(name: 'Emanuel', city: cities.first)
File without changes
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ </div>
24
+ </body>
25
+ </html>
File without changes
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-Agent: *
5
+ # Disallow: /
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
File without changes
File without changes
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ class AccountsControllerTest < ActionController::TestCase
4
+ test "should get index" do
5
+ get :index
6
+ assert_response :success
7
+ end
8
+
9
+ test "should get show" do
10
+ get :show
11
+ assert_response :success
12
+ end
13
+
14
+ test "should get transfer" do
15
+ get :transfer
16
+ assert_response :success
17
+ end
18
+
19
+ end
@@ -0,0 +1,12 @@
1
+ require 'test_helper'
2
+ require 'rails/performance_test_help'
3
+
4
+ class BrowsingTest < ActionDispatch::PerformanceTest
5
+ # Refer to the documentation for all available options
6
+ # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory]
7
+ # :output => 'tmp/performance', :formats => [:flat] }
8
+
9
+ def test_homepage
10
+ get '/'
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ # Add more helper methods to be used by all tests here...
7
+ end
File without changes
@@ -0,0 +1,4 @@
1
+ require 'test_helper'
2
+
3
+ class AccountsHelperTest < ActionView::TestCase
4
+ end
File without changes
@@ -0,0 +1,7 @@
1
+ require "active_model"
2
+
3
+ require "command_model/version"
4
+ require "command_model/model"
5
+
6
+ module CommandModel
7
+ end
@@ -0,0 +1,173 @@
1
+ module CommandModel
2
+ class Model
3
+ include ActiveModel::Validations
4
+ include ActiveModel::Conversion
5
+ extend ActiveModel::Naming
6
+
7
+ # Parameter requires one or more attributes as its first parameter(s).
8
+ # It accepts an options hash as its last parameter.
9
+ #
10
+ # ==== Options
11
+ #
12
+ # * typecast - The type of object to typecast to. Typecasts are built-in
13
+ # for integer, float, and date. Additional typecasts can be defined
14
+ # by defining a method typecast_#{name} for a typecast of #{name}.
15
+ # * validations - All other options are considered validations and are
16
+ # passed to ActiveModel::Validates.validates
17
+ #
18
+ # ==== Examples
19
+ #
20
+ # parameter :gender
21
+ # parameter :name, :presence => true
22
+ # parameter :birthdate, :typecast => :date
23
+ # parameter :height, :weight,
24
+ # :typecast => :integer,
25
+ # :presence => true,
26
+ # :numericality => { :greater_than_or_equal_to => 0 }
27
+ def self.parameter(*args)
28
+ options = args.last.kind_of?(Hash) ? args.pop.clone : {}
29
+ typecast = options.delete(:typecast)
30
+
31
+ args.each do |name|
32
+ attr_reader name
33
+
34
+ if typecast
35
+ attr_typecasting_writer name, typecast
36
+ else
37
+ attr_writer name
38
+ end
39
+ validates name, options.clone if options.present? # clone options because validates mutates the hash :(
40
+ end
41
+ end
42
+
43
+ def self.attr_typecasting_writer(name, target_type) #:nodoc
44
+ eval <<-END_EVAL
45
+ def #{name}=(value)
46
+ typecast_value = typecast_#{target_type}(value)
47
+ if typecast_value
48
+ @typecast_errors.delete("#{name}")
49
+ @#{name} = typecast_value
50
+ else
51
+ @typecast_errors["#{name}"] = "#{target_type}"
52
+ @#{name} = value
53
+ end
54
+
55
+ @#{name}
56
+ end
57
+ END_EVAL
58
+ end
59
+
60
+ # Executes a block of code if the command model is valid.
61
+ #
62
+ # Accepts either a command model or a hash of attributes with which to
63
+ # create a new command model.
64
+ #
65
+ # ==== Examples
66
+ #
67
+ # RenameUserCommand.execute(:login => "john") do |command|
68
+ # if allowed_to_rename_user?
69
+ # self.login = command.login
70
+ # else
71
+ # command.errors.add :base, "not allowed to rename"
72
+ # end
73
+ # end
74
+ def self.execute(attributes_or_command)
75
+ command = if attributes_or_command.kind_of? self
76
+ attributes_or_command
77
+ else
78
+ new(attributes_or_command)
79
+ end
80
+
81
+ yield command if command.valid?
82
+ command.execution_attempted!
83
+ command
84
+ end
85
+
86
+ # Quickly create a successful command object. This is used when the
87
+ # command takes no parameters to want to take advantage of the success?
88
+ # and errors properties of a command object.
89
+ def self.success
90
+ new.tap do |instance|
91
+ instance.execution_attempted!
92
+ end
93
+ end
94
+
95
+ # Quickly create a failed command object. Requires one parameter with
96
+ # the description of what went wrong. This is used when the
97
+ # command takes no parameters to want to take advantage of the success?
98
+ # and errors properties of a command object.
99
+ def self.failure(error)
100
+ new.tap do |instance|
101
+ instance.execution_attempted!
102
+ instance.errors.add(:base, error)
103
+ end
104
+ end
105
+
106
+ # Accepts an attributes hash
107
+ def initialize(attributes={})
108
+ @typecast_errors = {}
109
+
110
+ attributes.each do |k,v|
111
+ send "#{k}=", v
112
+ end
113
+ end
114
+
115
+ # Record that an attempt was made to execute this command whether or not
116
+ # it was successful.
117
+ def execution_attempted! #:nodoc:
118
+ @execution_attempted = true
119
+ end
120
+
121
+ # True if execution has been attempted on this command
122
+ def execution_attempted?
123
+ @execution_attempted
124
+ end
125
+
126
+ # Command has been executed without errors
127
+ def success?
128
+ execution_attempted? && errors.empty?
129
+ end
130
+
131
+ #:nodoc:
132
+ def persisted?
133
+ false
134
+ end
135
+
136
+ private
137
+ def typecast_integer(value)
138
+ Integer(value) rescue nil
139
+ end
140
+
141
+ def typecast_float(value)
142
+ Float(value) rescue nil
143
+ end
144
+
145
+ def typecast_date(value)
146
+ return value if value.kind_of? Date
147
+ value = value.to_s
148
+ if value =~ /\A(\d\d\d\d)-(\d\d)-(\d\d)\z/
149
+ Date.civil($1.to_i, $2.to_i, $3.to_i) rescue nil
150
+ else
151
+ Date.strptime(value, "%m/%d/%Y") rescue nil
152
+ end
153
+ end
154
+
155
+ def include_typecasting_errors
156
+ @typecast_errors.each do |attribute, target_type|
157
+ unless errors[attribute].present?
158
+ errors.add attribute, "is not a #{target_type}"
159
+ end
160
+ end
161
+ end
162
+
163
+ # overriding this to make typecasting errors run at the end so they will
164
+ # not run if there is already an error on the column. Otherwise, when
165
+ # typecasting to an integer and using validates_numericality_of two
166
+ # errors will be generated.
167
+ def run_validations!
168
+ super
169
+ include_typecasting_errors
170
+ errors.empty?
171
+ end
172
+ end
173
+ end