command_model 1.0.0

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