command_model 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,19 +1,26 @@
1
1
  # CommandModel
2
2
 
3
- Domain models usually have richer behavior than can be represented with a
4
- typical ActiveRecord style update_attributes.
3
+ CommandModel is an ActiveModel based class that encapsulates the user
4
+ interaction logic that wraps a domain operation. This user interaction
5
+ typically may include sanitizing, validating, normalizing, and typecasting
6
+ input. It also will include the response from the domain operation.
7
+
8
+ There are three major concerns when handling a user request: input handling,
9
+ domain logic, and persistence. ActiveRecord mixes all three of these concerns
10
+ together. While this is very convenient for simple CRUD, it becomes difficult
11
+ to work with once your domain operations become more complex. Domain models
12
+ usually have richer behavior than can be represented with a typical
13
+ ActiveRecord style update_attributes.
5
14
 
6
15
  # yuck!
7
- account.update_attributes :balance => account.balance - 50
16
+ account.update_attributes balance: account.balance - 50
8
17
 
9
18
  # much better
10
- account.withdraw :amount => 50
19
+ account.withdraw amount: 50
11
20
 
12
21
  But there are multiple complications with the OO approach. How do we integrate
13
22
  Rails style validations? How are user-supplied strings typecast? How do we
14
- know if the command succeeded? CommandModel solves these problems. CommandModel
15
- is an ActiveModel based class that encapsulates validations and typecasting
16
- for command execution.
23
+ know if the command succeeded? CommandModel solves these problems.
17
24
 
18
25
  ## Installation
19
26
 
@@ -36,25 +43,23 @@ request.
36
43
 
37
44
  class WithdrawCommand < CommandModel::Model
38
45
  parameter :amount,
39
- :typecast => :integer,
40
- :presence => true,
41
- :numericality => { :greater_than => 0, :less_than_or_equal_to => 500 }
46
+ typecast: :integer,
47
+ presence: true,
48
+ numericality: { greater_than: 0, less_than_or_equal_to: 500 }
42
49
  end
43
50
 
44
- Create the method to run the command. This method should call the class method
45
- execute on the command class and pass it the options it received. It will
46
- accept either a command object or a hash of attributes. It must pass execute
51
+ Create the method to run the command. This method should instantiate and call a new command object. It must pass call
47
52
  a block that actually does the work. The block will only be called if
48
- the validations in the command object pass. The execute block is free to do
53
+ the validations in the command object pass. The block is free to do
49
54
  any further validations that only can be done during execution. If it adds
50
55
  any errors to the command object then the command will be considered to have
51
- failed. Finally the execute method will return the command object.
56
+ failed. Finally, the call method will return self.
52
57
 
53
58
  class Account
54
59
  # ...
55
60
 
56
- def withdraw(options)
57
- WithdrawCommand.execute(options) do |command|
61
+ def withdraw(args)
62
+ WithdrawCommand.new(args).call do |command|
58
63
  if balance >= command.amount
59
64
  @balance -= command.amount
60
65
  else
@@ -68,7 +73,7 @@ failed. Finally the execute method will return the command object.
68
73
 
69
74
  Use example:
70
75
 
71
- response = account.withdraw :amount => 50
76
+ response = account.withdraw amount: 50
72
77
 
73
78
  if response.success?
74
79
  puts "Success!"
@@ -76,7 +81,38 @@ Use example:
76
81
  puts "Errors:"
77
82
  puts response.errors.full_messages
78
83
  end
79
-
84
+
85
+ ## Mixing in Domain Logic
86
+
87
+ In a pure OO world the domain logic for actually executing a command may
88
+ belong in another class. However, it is possible to mix in that logic directly
89
+ into the command object. This can easily be done by overriding the execute
90
+ method. The execute method is called by the call method if all validations
91
+ succeed. The following is a reimplementation of the previous example with
92
+ internal domain logic.
93
+
94
+ class WithdrawCommand < CommandModel::Model
95
+ parameter :amount,
96
+ typecast: :integer,
97
+ presence: true,
98
+ numericality: { greater_than: 0, less_than_or_equal_to: 500 }
99
+ parameter :account_id, presence: true
100
+
101
+ def execute
102
+ account = Account.find_by_id account_id
103
+ unless account
104
+ errors.add :account_id, "not found"
105
+ return
106
+ end
107
+
108
+ if account.balance >= amount
109
+ account.balance -= amount
110
+ else
111
+ errors.add :amount, "is more than account balance"
112
+ end
113
+ end
114
+ end
115
+
80
116
  ## Other uses
81
117
 
82
118
  This could be used to wrap database generated errors into normal Rails
@@ -85,11 +121,22 @@ show up in errors the same as validates_uniqueness_of. validates_uniqueness_of
85
121
  could even be removed for a marginal performance boost as the database should
86
122
  be doing a uniqueness check anyway.
87
123
 
88
- # Examples
124
+ ## Examples
89
125
 
90
126
  There is a simple Rails application in examples/bank that demonstrates the
91
127
  integration of Rails form helpers and validations with CommandModel.
92
128
 
129
+ ## Version History
130
+
131
+ * 1.1 - November 13, 2012
132
+ ** Updated documentation and example application
133
+ ** Refactored Model to support internal domain logic easier with #call and #execute.
134
+ ** Model#initialize can now copy another model
135
+ ** Added Model#set_parameters
136
+ ** Added Model.parameters
137
+ * 1.0 - April 14, 2012
138
+ ** Initial public release
139
+
93
140
  ## Contributing
94
141
 
95
142
  1. Fork it
@@ -17,7 +17,9 @@ Gem::Specification.new do |gem|
17
17
 
18
18
  gem.add_dependency 'activemodel', "~> 3.2"
19
19
 
20
- gem.add_development_dependency 'rspec', "~> 2.9.0"
21
- gem.add_development_dependency 'guard', "~> 1.0.0"
22
- gem.add_development_dependency 'guard-rspec', "~> 0.7.0"
20
+ gem.add_development_dependency 'rspec', "~> 2.11.0"
21
+ gem.add_development_dependency 'guard', "~> 1.5.3"
22
+ gem.add_development_dependency 'guard-rspec', "~> 2.1.1"
23
+ gem.add_development_dependency 'rb-fsevent', '~> 0.9.1'
24
+
23
25
  end
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'rails', '3.2.3'
3
+ gem 'rails', '3.2.8'
4
4
 
5
5
  # Bundle edge Rails instead:
6
6
  # gem 'rails', :git => 'git://github.com/rails/rails.git'
@@ -0,0 +1,7 @@
1
+ # Bank
2
+
3
+ This is an example application that gives several different uses of
4
+ CommandModel in a Rails application.
5
+
6
+ Withdrawals and deposits are modeled as instance methods of Account. Transfers
7
+ are modeled as their own entity. CommandModel easily supports both.
@@ -1,15 +1,15 @@
1
- class AccountsController < ApplicationController
1
+ class AccountsController < ApplicationController
2
2
  def index
3
- @accounts = ACCOUNTS
3
+ @accounts = Account.all
4
4
  end
5
5
 
6
6
  def deposit_form
7
- @account = find_account_by_name params[:id]
7
+ @account = Account.find_by_name params[:id]
8
8
  @deposit = Account::DepositCommand.new
9
9
  end
10
10
 
11
11
  def deposit
12
- @account = find_account_by_name params[:id]
12
+ @account = Account.find_by_name params[:id]
13
13
  @deposit = @account.deposit params[:deposit]
14
14
 
15
15
  if @deposit.success?
@@ -20,12 +20,12 @@ class AccountsController < ApplicationController
20
20
  end
21
21
 
22
22
  def withdraw_form
23
- @account = find_account_by_name params[:id]
23
+ @account = Account.find_by_name params[:id]
24
24
  @withdraw = Account::WithdrawCommand.new
25
25
  end
26
26
 
27
27
  def withdraw
28
- @account = find_account_by_name params[:id]
28
+ @account = Account.find_by_name params[:id]
29
29
  @withdraw = @account.withdraw params[:withdraw]
30
30
 
31
31
  if @withdraw.success?
@@ -36,27 +36,18 @@ class AccountsController < ApplicationController
36
36
  end
37
37
 
38
38
  def transfer_form
39
- @accounts = ACCOUNTS
39
+ @accounts = Account.all
40
40
  @transfer = Account::TransferCommand.new
41
41
  end
42
42
 
43
43
  def transfer
44
- @accounts = ACCOUNTS
45
- @transfer = Account::TransferCommand.new
46
- @transfer.to = find_account_by_name params[:transfer][:to]
47
- @transfer.from = find_account_by_name params[:transfer][:from]
48
- @transfer.amount = params[:transfer][:amount]
49
- Account.transfer @transfer
44
+ @transfer = Account::TransferCommand.new(params[:transfer])
50
45
 
51
- if @transfer.success?
46
+ if @transfer.call.success?
52
47
  redirect_to root_path, :notice => "Transferred #{@transfer.amount} from #{@transfer.from.name}'s account to #{@transfer.to.name}'s account."
53
48
  else
49
+ @accounts = Account.all
54
50
  render "transfer_form"
55
51
  end
56
52
  end
57
-
58
- private
59
- def find_account_by_name(name)
60
- ACCOUNTS.find { |a| a.name == name }
61
- end
62
53
  end
@@ -0,0 +1,17 @@
1
+ class TransfersController < ApplicationController
2
+ def new
3
+ @accounts = Account.all
4
+ @transfer = Account::Transfer.new
5
+ end
6
+
7
+ def create
8
+ @transfer = Account::Transfer.new(params[:transfer])
9
+
10
+ if @transfer.call.success?
11
+ redirect_to root_path, :notice => "Transferred #{@transfer.amount} from #{@transfer.from.name}'s account to #{@transfer.to.name}'s account."
12
+ else
13
+ @accounts = Account.all
14
+ render :new
15
+ end
16
+ end
17
+ end
@@ -1,29 +1,17 @@
1
1
  class Account
2
2
  class WithdrawCommand < CommandModel::Model
3
3
  parameter :amount,
4
- :typecast => :integer,
5
- :presence => true,
6
- :numericality => { :greater_than => 0, :less_than_or_equal_to => 500 }
4
+ typecast: :integer,
5
+ presence: true,
6
+ numericality: { greater_than: 0, less_than_or_equal_to: 500 }
7
7
  end
8
8
 
9
9
  class DepositCommand < CommandModel::Model
10
10
  parameter :amount,
11
- :typecast => :integer,
12
- :presence => true,
13
- :numericality => { :greater_than => 0 }
14
- end
15
-
16
- class TransferCommand < CommandModel::Model
17
- parameter :from, :to, :presence => true
18
- parameter :amount,
19
- :typecast => :integer,
20
- :presence => true,
21
- :numericality => { :greater_than => 0 }
22
-
23
- validate do |model|
24
- errors.add :base, "From and to accounts cannot be the same" if model.from == model.to
25
- end
26
- end
11
+ typecast: :integer,
12
+ presence: true,
13
+ numericality: { greater_than: 0 }
14
+ end
27
15
 
28
16
  attr_reader :name, :balance
29
17
 
@@ -32,8 +20,8 @@ class Account
32
20
  @balance = balance
33
21
  end
34
22
 
35
- def withdraw(options)
36
- WithdrawCommand.execute(options) do |command|
23
+ def withdraw(args)
24
+ WithdrawCommand.new(args).call do |command|
37
25
  if balance >= command.amount
38
26
  @balance -= command.amount
39
27
  else
@@ -42,24 +30,21 @@ class Account
42
30
  end
43
31
  end
44
32
 
45
- def deposit(options)
46
- DepositCommand.execute(options) do |command|
33
+ def deposit(args)
34
+ DepositCommand.new(args).call do |command|
47
35
  @balance += command.amount
48
36
  end
49
37
  end
50
38
 
51
- def self.transfer(options)
52
- TransferCommand.execute(options) do |command|
53
- if command.from.balance >= command.amount
54
- command.from.withdraw :amount => command.amount
55
- command.to.deposit :amount => command.amount
56
- else
57
- command.errors.add :amount, "is more than account balance"
58
- end
59
- end
60
- end
61
-
62
39
  def to_param
63
40
  name
64
41
  end
42
+
43
+ def self.all
44
+ ACCOUNTS
45
+ end
46
+
47
+ def self.find_by_name(name)
48
+ all.find { |a| a.name == name }
49
+ end
65
50
  end
@@ -0,0 +1,34 @@
1
+ class Transfer < CommandModel::Model
2
+ parameter :from_name, :to_name, presence: true
3
+ parameter :amount,
4
+ typecast: :integer,
5
+ presence: true,
6
+ numericality: { greater_than: 0 }
7
+
8
+ validate do |model|
9
+ { from_name: from, to_name: to }.each do |key, value|
10
+ errors.add key, "is not a valid account name" unless value
11
+ end
12
+ end
13
+
14
+ validate do |model|
15
+ errors.add :base, "From and to accounts cannot be the same" if model.from == model.to
16
+ end
17
+
18
+ def call
19
+ if from.balance >= amount
20
+ from.withdraw amount: amount
21
+ to.deposit amount: amount
22
+ else
23
+ errors.add :amount, "is more than account balance"
24
+ end
25
+ end
26
+
27
+ def from
28
+ Account.find_by_name from_name
29
+ end
30
+
31
+ def to
32
+ Account.find_by_name to_name
33
+ end
34
+ end
@@ -19,4 +19,4 @@
19
19
  <% end %>
20
20
  </table>
21
21
 
22
- <%= link_to "Transfer", account_transfer_path %>
22
+ <%= link_to "Transfer", new_transfer_path %>
@@ -0,0 +1,32 @@
1
+ <p><%= link_to "Back to all accounts", root_path %></p>
2
+
3
+ <h1>Transfer Between Accounts</h1>
4
+
5
+
6
+ <% if @transfer.errors.present? %>
7
+ <ul>
8
+ <% @transfer.errors.full_messages.each do |msg| %>
9
+ <li><%= msg %></li>
10
+ <% end %>
11
+ </ul>
12
+ <% end %>
13
+
14
+ <%= form_for @transfer do |f| %>
15
+ <div>
16
+ <%= f.label :from_name %><br />
17
+ <%= f.select :from_name, @accounts.map { |a| ["#{a.name} - #{a.balance}", a.name] }, :selected => f.object.from_name, :include_blank => true %>
18
+ </div>
19
+ <div>
20
+ <%= f.label :to_name %><br />
21
+ <%= f.select :to_name, @accounts.map { |a| ["#{a.name} - #{a.balance}", a.name] }, :selected => f.object.to_name, :include_blank => true %>
22
+ </div>
23
+ <div>
24
+ <%= f.label :amount %><br />
25
+ <%= f.text_field :amount %>
26
+ </div>
27
+ <%= f.submit "Transfer" %>
28
+ <% end %>
29
+
30
+
31
+
32
+
@@ -5,8 +5,7 @@ Bank::Application.routes.draw do
5
5
  get "accounts/:id/deposit" => "accounts#deposit_form", :as => :account_deposit_form
6
6
  post "accounts/:id/deposit" => "accounts#deposit", :as => :account_deposit
7
7
 
8
- get "accounts/transfer" => "accounts#transfer_form", :as => :account_transfer_form
9
- post "accounts/transfer" => "accounts#transfer", :as => :account_transfer
10
-
8
+ resources :transfers, only: %w[new create]
9
+
11
10
  root :to => "accounts#index"
12
11
  end
@@ -3,6 +3,8 @@ module CommandModel
3
3
  include ActiveModel::Validations
4
4
  include ActiveModel::Conversion
5
5
  extend ActiveModel::Naming
6
+
7
+ Parameter = Struct.new(:name, :typecast, :validations)
6
8
 
7
9
  # Parameter requires one or more attributes as its first parameter(s).
8
10
  # It accepts an options hash as its last parameter.
@@ -37,8 +39,14 @@ module CommandModel
37
39
  attr_writer name
38
40
  end
39
41
  validates name, options.clone if options.present? # clone options because validates mutates the hash :(
42
+ parameters.push Parameter.new name, typecast, options
40
43
  end
41
44
  end
45
+
46
+ # Returns array of all parameters defined for class
47
+ def self.parameters
48
+ @parameters ||= []
49
+ end
42
50
 
43
51
  def self.attr_typecasting_writer(name, target_type) #:nodoc
44
52
  eval <<-END_EVAL
@@ -71,16 +79,14 @@ module CommandModel
71
79
  # command.errors.add :base, "not allowed to rename"
72
80
  # end
73
81
  # end
74
- def self.execute(attributes_or_command)
82
+ def self.execute(attributes_or_command, &block)
75
83
  command = if attributes_or_command.kind_of? self
76
84
  attributes_or_command
77
85
  else
78
86
  new(attributes_or_command)
79
87
  end
80
88
 
81
- yield command if command.valid?
82
- command.execution_attempted!
83
- command
89
+ command.call &block
84
90
  end
85
91
 
86
92
  # Quickly create a successful command object. This is used when the
@@ -103,15 +109,31 @@ module CommandModel
103
109
  end
104
110
  end
105
111
 
106
- # Accepts an attributes hash
107
- def initialize(attributes={})
112
+ # Accepts a parameters hash or another of the same class. If another
113
+ # instance of the same class is passed in then the parameters are copied
114
+ # to the new object.
115
+ def initialize(parameters={})
108
116
  @typecast_errors = {}
109
-
110
- attributes.each do |k,v|
111
- send "#{k}=", v
112
- end
117
+ set_parameters parameters
113
118
  end
114
-
119
+
120
+ # Executes the command by calling the method +execute+ if the validations
121
+ # pass.
122
+ def call(&block)
123
+ execute(&block) if valid?
124
+ execution_attempted!
125
+ self
126
+ end
127
+
128
+ # Performs the actual command execution. It does not test if the command
129
+ # parameters are valid. Typically, +call+ should be called instead of
130
+ # calling +execute+ directly.
131
+ #
132
+ # +execute+ should be overridden in descendent classes
133
+ def execute
134
+ yield self if block_given?
135
+ end
136
+
115
137
  # Record that an attempt was made to execute this command whether or not
116
138
  # it was successful.
117
139
  def execution_attempted! #:nodoc:
@@ -127,6 +149,21 @@ module CommandModel
127
149
  def success?
128
150
  execution_attempted? && errors.empty?
129
151
  end
152
+
153
+ # Returns hash of all parameter names and values
154
+ def parameters
155
+ self.class.parameters.each_with_object({}) do |parameter, hash|
156
+ hash[parameter.name] = send(parameter.name)
157
+ end
158
+ end
159
+
160
+ # Sets parameter(s) from hash or instance of same class
161
+ def set_parameters(hash_or_instance)
162
+ parameters = extract_parameters_from_hash_or_instance(hash_or_instance)
163
+ parameters.each do |k,v|
164
+ send "#{k}=", v
165
+ end
166
+ end
130
167
 
131
168
  #:nodoc:
132
169
  def persisted?
@@ -134,6 +171,14 @@ module CommandModel
134
171
  end
135
172
 
136
173
  private
174
+ def extract_parameters_from_hash_or_instance(hash_or_instance)
175
+ if hash_or_instance.respond_to?(:parameters)
176
+ hash_or_instance.parameters
177
+ else
178
+ hash_or_instance
179
+ end
180
+ end
181
+
137
182
  def typecast_integer(value)
138
183
  Integer(value) rescue nil
139
184
  end
@@ -1,3 +1,3 @@
1
1
  module CommandModel
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
data/spec/model_spec.rb CHANGED
@@ -2,6 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  class ExampleCommand < CommandModel::Model
4
4
  parameter :name, :presence => true
5
+ parameter :title
5
6
  end
6
7
 
7
8
  describe CommandModel::Model do
@@ -66,7 +67,21 @@ describe CommandModel::Model do
66
67
  instance.errors[:name].should be_present
67
68
  end
68
69
  end
69
-
70
+
71
+ describe "self.parameters" do
72
+ it "returns all parameters in class" do
73
+ klass = Class.new(CommandModel::Model)
74
+ klass.parameter :name, presence: true
75
+ klass.parameter :birthdate, typecast: :date, presence: true
76
+
77
+ expected = [
78
+ CommandModel::Model::Parameter.new(:name, nil, { presence: true }),
79
+ CommandModel::Model::Parameter.new(:birthdate, :date, { presence: true })
80
+ ]
81
+
82
+ expect(klass.parameters).to eq(expected)
83
+ end
84
+ end
70
85
 
71
86
  describe "self.execute" do
72
87
  it "accepts object of same kind and returns it" do
@@ -129,10 +144,51 @@ describe CommandModel::Model do
129
144
 
130
145
 
131
146
  describe "initialize" do
132
- it "assigns attributes" do
147
+ it "assigns parameters from hash" do
133
148
  m = ExampleCommand.new :name => "John"
134
149
  m.name.should eq("John")
135
150
  end
151
+
152
+ it "assigns parameters from other CommandModel" do
153
+ other = ExampleCommand.new :name => "John"
154
+ m = ExampleCommand.new other
155
+ expect(m.name).to eq(other.name)
156
+ end
157
+ end
158
+
159
+ describe "call" do
160
+ context "when valid" do
161
+ it "calls execute" do
162
+ example_command.should_receive(:execute)
163
+ example_command.call
164
+ end
165
+
166
+ it "returns self" do
167
+ expect(example_command.call).to eq(example_command)
168
+ end
169
+ end
170
+
171
+ context "when invalid" do
172
+ it "does not call execute" do
173
+ invalid_example_command.should_not_receive(:execute)
174
+ invalid_example_command.call
175
+ end
176
+
177
+ it "returns self" do
178
+ expect(invalid_example_command.call).to eq(invalid_example_command)
179
+ end
180
+ end
181
+ end
182
+
183
+ describe "execute" do
184
+ it "yields to block with self as argument" do
185
+ block_arg = nil
186
+ example_command.execute do |command|
187
+ block_arg = command
188
+ end
189
+
190
+ expect(block_arg).to eq(example_command)
191
+ end
136
192
  end
137
193
 
138
194
  describe "execution_attempted!" do
@@ -158,6 +214,32 @@ describe CommandModel::Model do
158
214
  example_command.success?.should eq(true)
159
215
  end
160
216
  end
217
+
218
+ describe "parameters" do
219
+ it "is a hash of all parameter name and values" do
220
+ klass = Class.new(CommandModel::Model)
221
+ klass.parameter :name, presence: true
222
+ klass.parameter :birthdate, typecast: :date, presence: true
223
+
224
+ expected = { name: "John", birthdate: Date.new(1980,1,1) }
225
+ instance = klass.new expected
226
+ expect(instance.parameters).to eq(expected)
227
+ end
228
+ end
229
+
230
+ describe "set_parameters" do
231
+ it "sets parameters from hash with symbol keys" do
232
+ example_command.set_parameters name: "Bill", title: "Boss"
233
+ expect(example_command.name).to eq("Bill")
234
+ expect(example_command.title).to eq("Boss")
235
+ end
236
+
237
+ it "sets parameters from hash with string keys" do
238
+ example_command.set_parameters "name" => "Bill", "title" => "Boss"
239
+ expect(example_command.name).to eq("Bill")
240
+ expect(example_command.title).to eq("Boss")
241
+ end
242
+ end
161
243
 
162
244
  describe "typecast_integer" do
163
245
  it "casts to integer when valid string" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: command_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-14 00:00:00.000000000 Z
12
+ date: 2012-11-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
16
- requirement: &15163980 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,40 +21,76 @@ dependencies:
21
21
  version: '3.2'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *15163980
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.2'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rspec
27
- requirement: &15163060 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
31
36
  - !ruby/object:Gem::Version
32
- version: 2.9.0
37
+ version: 2.11.0
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *15163060
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.11.0
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: guard
38
- requirement: &15162120 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ~>
42
52
  - !ruby/object:Gem::Version
43
- version: 1.0.0
53
+ version: 1.5.3
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *15162120
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.5.3
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: guard-rspec
49
- requirement: &15161240 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.1.1
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 2.1.1
78
+ - !ruby/object:Gem::Dependency
79
+ name: rb-fsevent
80
+ requirement: !ruby/object:Gem::Requirement
50
81
  none: false
51
82
  requirements:
52
83
  - - ~>
53
84
  - !ruby/object:Gem::Version
54
- version: 0.7.0
85
+ version: 0.9.1
55
86
  type: :development
56
87
  prerelease: false
57
- version_requirements: *15161240
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.9.1
58
94
  description: CommandModel - when update_attributes isn't enough.
59
95
  email:
60
96
  - jack@jackchristensen.com
@@ -72,7 +108,7 @@ files:
72
108
  - command_model.gemspec
73
109
  - examples/bank/.gitignore
74
110
  - examples/bank/Gemfile
75
- - examples/bank/README.rdoc
111
+ - examples/bank/README.md
76
112
  - examples/bank/Rakefile
77
113
  - examples/bank/app/assets/images/rails.png
78
114
  - examples/bank/app/assets/javascripts/accounts.js.coffee
@@ -81,16 +117,18 @@ files:
81
117
  - examples/bank/app/assets/stylesheets/application.css
82
118
  - examples/bank/app/controllers/accounts_controller.rb
83
119
  - examples/bank/app/controllers/application_controller.rb
120
+ - examples/bank/app/controllers/transfers_controller.rb
84
121
  - examples/bank/app/helpers/accounts_helper.rb
85
122
  - examples/bank/app/helpers/application_helper.rb
86
123
  - examples/bank/app/mailers/.gitkeep
87
124
  - examples/bank/app/models/.gitkeep
88
125
  - examples/bank/app/models/account.rb
126
+ - examples/bank/app/models/transfer.rb
89
127
  - examples/bank/app/views/accounts/deposit_form.html.erb
90
128
  - examples/bank/app/views/accounts/index.html.erb
91
- - examples/bank/app/views/accounts/transfer_form.html.erb
92
129
  - examples/bank/app/views/accounts/withdraw_form.html.erb
93
130
  - examples/bank/app/views/layouts/application.html.erb
131
+ - examples/bank/app/views/transfers/new.html.erb
94
132
  - examples/bank/config.ru
95
133
  - examples/bank/config/application.rb
96
134
  - examples/bank/config/boot.rb
@@ -153,7 +191,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
191
  version: '0'
154
192
  requirements: []
155
193
  rubyforge_project:
156
- rubygems_version: 1.8.17
194
+ rubygems_version: 1.8.24
157
195
  signing_key:
158
196
  specification_version: 3
159
197
  summary: CommandModel integrates Rails validations with command objects. This allows
@@ -1,261 +0,0 @@
1
- == Welcome to Rails
2
-
3
- Rails is a web-application framework that includes everything needed to create
4
- database-backed web applications according to the Model-View-Control pattern.
5
-
6
- This pattern splits the view (also called the presentation) into "dumb"
7
- templates that are primarily responsible for inserting pre-built data in between
8
- HTML tags. The model contains the "smart" domain objects (such as Account,
9
- Product, Person, Post) that holds all the business logic and knows how to
10
- persist themselves to a database. The controller handles the incoming requests
11
- (such as Save New Account, Update Product, Show Post) by manipulating the model
12
- and directing data to the view.
13
-
14
- In Rails, the model is handled by what's called an object-relational mapping
15
- layer entitled Active Record. This layer allows you to present the data from
16
- database rows as objects and embellish these data objects with business logic
17
- methods. You can read more about Active Record in
18
- link:files/vendor/rails/activerecord/README.html.
19
-
20
- The controller and view are handled by the Action Pack, which handles both
21
- layers by its two parts: Action View and Action Controller. These two layers
22
- are bundled in a single package due to their heavy interdependence. This is
23
- unlike the relationship between the Active Record and Action Pack that is much
24
- more separate. Each of these packages can be used independently outside of
25
- Rails. You can read more about Action Pack in
26
- link:files/vendor/rails/actionpack/README.html.
27
-
28
-
29
- == Getting Started
30
-
31
- 1. At the command prompt, create a new Rails application:
32
- <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
33
-
34
- 2. Change directory to <tt>myapp</tt> and start the web server:
35
- <tt>cd myapp; rails server</tt> (run with --help for options)
36
-
37
- 3. Go to http://localhost:3000/ and you'll see:
38
- "Welcome aboard: You're riding Ruby on Rails!"
39
-
40
- 4. Follow the guidelines to start developing your application. You can find
41
- the following resources handy:
42
-
43
- * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
44
- * Ruby on Rails Tutorial Book: http://www.railstutorial.org/
45
-
46
-
47
- == Debugging Rails
48
-
49
- Sometimes your application goes wrong. Fortunately there are a lot of tools that
50
- will help you debug it and get it back on the rails.
51
-
52
- First area to check is the application log files. Have "tail -f" commands
53
- running on the server.log and development.log. Rails will automatically display
54
- debugging and runtime information to these files. Debugging info will also be
55
- shown in the browser on requests from 127.0.0.1.
56
-
57
- You can also log your own messages directly into the log file from your code
58
- using the Ruby logger class from inside your controllers. Example:
59
-
60
- class WeblogController < ActionController::Base
61
- def destroy
62
- @weblog = Weblog.find(params[:id])
63
- @weblog.destroy
64
- logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
65
- end
66
- end
67
-
68
- The result will be a message in your log file along the lines of:
69
-
70
- Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
71
-
72
- More information on how to use the logger is at http://www.ruby-doc.org/core/
73
-
74
- Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
75
- several books available online as well:
76
-
77
- * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
78
- * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
79
-
80
- These two books will bring you up to speed on the Ruby language and also on
81
- programming in general.
82
-
83
-
84
- == Debugger
85
-
86
- Debugger support is available through the debugger command when you start your
87
- Mongrel or WEBrick server with --debugger. This means that you can break out of
88
- execution at any point in the code, investigate and change the model, and then,
89
- resume execution! You need to install ruby-debug to run the server in debugging
90
- mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
91
-
92
- class WeblogController < ActionController::Base
93
- def index
94
- @posts = Post.all
95
- debugger
96
- end
97
- end
98
-
99
- So the controller will accept the action, run the first line, then present you
100
- with a IRB prompt in the server window. Here you can do things like:
101
-
102
- >> @posts.inspect
103
- => "[#<Post:0x14a6be8
104
- @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
105
- #<Post:0x14a6620
106
- @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
107
- >> @posts.first.title = "hello from a debugger"
108
- => "hello from a debugger"
109
-
110
- ...and even better, you can examine how your runtime objects actually work:
111
-
112
- >> f = @posts.first
113
- => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
114
- >> f.
115
- Display all 152 possibilities? (y or n)
116
-
117
- Finally, when you're ready to resume execution, you can enter "cont".
118
-
119
-
120
- == Console
121
-
122
- The console is a Ruby shell, which allows you to interact with your
123
- application's domain model. Here you'll have all parts of the application
124
- configured, just like it is when the application is running. You can inspect
125
- domain models, change values, and save to the database. Starting the script
126
- without arguments will launch it in the development environment.
127
-
128
- To start the console, run <tt>rails console</tt> from the application
129
- directory.
130
-
131
- Options:
132
-
133
- * Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications
134
- made to the database.
135
- * Passing an environment name as an argument will load the corresponding
136
- environment. Example: <tt>rails console production</tt>.
137
-
138
- To reload your controllers and models after launching the console run
139
- <tt>reload!</tt>
140
-
141
- More information about irb can be found at:
142
- link:http://www.rubycentral.org/pickaxe/irb.html
143
-
144
-
145
- == dbconsole
146
-
147
- You can go to the command line of your database directly through <tt>rails
148
- dbconsole</tt>. You would be connected to the database with the credentials
149
- defined in database.yml. Starting the script without arguments will connect you
150
- to the development database. Passing an argument will connect you to a different
151
- database, like <tt>rails dbconsole production</tt>. Currently works for MySQL,
152
- PostgreSQL and SQLite 3.
153
-
154
- == Description of Contents
155
-
156
- The default directory structure of a generated Ruby on Rails application:
157
-
158
- |-- app
159
- | |-- assets
160
- | |-- images
161
- | |-- javascripts
162
- | `-- stylesheets
163
- | |-- controllers
164
- | |-- helpers
165
- | |-- mailers
166
- | |-- models
167
- | `-- views
168
- | `-- layouts
169
- |-- config
170
- | |-- environments
171
- | |-- initializers
172
- | `-- locales
173
- |-- db
174
- |-- doc
175
- |-- lib
176
- | `-- tasks
177
- |-- log
178
- |-- public
179
- |-- script
180
- |-- test
181
- | |-- fixtures
182
- | |-- functional
183
- | |-- integration
184
- | |-- performance
185
- | `-- unit
186
- |-- tmp
187
- | |-- cache
188
- | |-- pids
189
- | |-- sessions
190
- | `-- sockets
191
- `-- vendor
192
- |-- assets
193
- `-- stylesheets
194
- `-- plugins
195
-
196
- app
197
- Holds all the code that's specific to this particular application.
198
-
199
- app/assets
200
- Contains subdirectories for images, stylesheets, and JavaScript files.
201
-
202
- app/controllers
203
- Holds controllers that should be named like weblogs_controller.rb for
204
- automated URL mapping. All controllers should descend from
205
- ApplicationController which itself descends from ActionController::Base.
206
-
207
- app/models
208
- Holds models that should be named like post.rb. Models descend from
209
- ActiveRecord::Base by default.
210
-
211
- app/views
212
- Holds the template files for the view that should be named like
213
- weblogs/index.html.erb for the WeblogsController#index action. All views use
214
- eRuby syntax by default.
215
-
216
- app/views/layouts
217
- Holds the template files for layouts to be used with views. This models the
218
- common header/footer method of wrapping views. In your views, define a layout
219
- using the <tt>layout :default</tt> and create a file named default.html.erb.
220
- Inside default.html.erb, call <% yield %> to render the view using this
221
- layout.
222
-
223
- app/helpers
224
- Holds view helpers that should be named like weblogs_helper.rb. These are
225
- generated for you automatically when using generators for controllers.
226
- Helpers can be used to wrap functionality for your views into methods.
227
-
228
- config
229
- Configuration files for the Rails environment, the routing map, the database,
230
- and other dependencies.
231
-
232
- db
233
- Contains the database schema in schema.rb. db/migrate contains all the
234
- sequence of Migrations for your schema.
235
-
236
- doc
237
- This directory is where your application documentation will be stored when
238
- generated using <tt>rake doc:app</tt>
239
-
240
- lib
241
- Application specific libraries. Basically, any kind of custom code that
242
- doesn't belong under controllers, models, or helpers. This directory is in
243
- the load path.
244
-
245
- public
246
- The directory available for the web server. Also contains the dispatchers and the
247
- default HTML files. This should be set as the DOCUMENT_ROOT of your web
248
- server.
249
-
250
- script
251
- Helper scripts for automation and generation.
252
-
253
- test
254
- Unit and functional tests along with fixtures. When using the rails generate
255
- command, template test files will be generated for you and placed in this
256
- directory.
257
-
258
- vendor
259
- External libraries that the application depends on. Also includes the plugins
260
- subdirectory. If the app has frozen rails, those gems also go here, under
261
- vendor/rails/. This directory is in the load path.
@@ -1,32 +0,0 @@
1
- <p><%= link_to "Back to all accounts", root_path %></p>
2
-
3
- <h1>Transfer Between Accounts</h1>
4
-
5
-
6
- <% if @transfer.errors.present? %>
7
- <ul>
8
- <% @transfer.errors.full_messages.each do |msg| %>
9
- <li><%= msg %></li>
10
- <% end %>
11
- </ul>
12
- <% end %>
13
-
14
- <%= form_for @transfer, :as => :transfer, :url => account_transfer_path do |f| %>
15
- <div>
16
- <%= f.label :from %><br />
17
- <%= f.select :from, @accounts.map { |a| ["#{a.name} - #{a.balance}", a.name] }, :selected => f.object.from.try(:name), :include_blank => true %>
18
- </div>
19
- <div>
20
- <%= f.label :to %><br />
21
- <%= f.select :to, @accounts.map { |a| ["#{a.name} - #{a.balance}", a.name] }, :selected => f.object.to.try(:name), :include_blank => true %>
22
- </div>
23
- <div>
24
- <%= f.label :amount %><br />
25
- <%= f.text_field :amount %>
26
- </div>
27
- <%= f.submit "Transfer" %>
28
- <% end %>
29
-
30
-
31
-
32
-