motion_model 0.4.1 → 0.4.2
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.
- data/CHANGELOG +16 -5
- data/Gemfile +4 -0
- data/README.md +46 -0
- data/Rakefile +4 -1
- data/lib/motion_model/adapters/array_model_adapter.rb +52 -28
- data/lib/motion_model/adapters/array_model_persistence.rb +141 -0
- data/lib/motion_model/ext.rb +17 -0
- data/lib/motion_model/model/array_finder_query.rb +6 -1
- data/lib/motion_model/model/column.rb +24 -8
- data/lib/motion_model/model/formotion.rb +4 -4
- data/lib/motion_model/model/model.rb +360 -92
- data/lib/motion_model/model/model_casts.rb +13 -8
- data/lib/motion_model/model/transaction.rb +1 -1
- data/lib/motion_model/validatable.rb +29 -15
- data/lib/motion_model/version.rb +1 -1
- data/spec/adapter_spec.rb +30 -0
- data/spec/array_model_persistence_spec.rb +229 -0
- data/spec/cascading_delete_spec.rb +5 -5
- data/spec/date_spec.rb +26 -13
- data/spec/finder_spec.rb +1 -0
- data/spec/formotion_spec.rb +1 -0
- data/spec/model_hook_spec.rb +5 -4
- data/spec/model_spec.rb +1 -7
- data/spec/relation_spec.rb +10 -13
- metadata +66 -49
@@ -6,7 +6,7 @@ module MotionModel
|
|
6
6
|
when TrueClass, FalseClass then arg
|
7
7
|
when Integer then arg != 0
|
8
8
|
when String then (arg =~ /^true/i) != nil
|
9
|
-
else raise ArgumentError.new("type #{column_name} : #{
|
9
|
+
else raise ArgumentError.new("type #{column_name} : #{column_type(column_name)} is not possible to cast.")
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -23,14 +23,18 @@ module MotionModel
|
|
23
23
|
when String
|
24
24
|
return NSDate.dateWithNaturalLanguageString(arg.gsub('-','/'), locale:NSUserDefaults.standardUserDefaults.dictionaryRepresentation)
|
25
25
|
when Time
|
26
|
-
return NSDate.dateWithNaturalLanguageString(arg.strftime('%Y/%m/%d %H:%M'), locale:NSUserDefaults.standardUserDefaults.dictionaryRepresentation)
|
26
|
+
return NSDate.dateWithNaturalLanguageString(arg.strftime('%Y/%m/%d %H:%M:%S'), locale:NSUserDefaults.standardUserDefaults.dictionaryRepresentation)
|
27
27
|
else
|
28
28
|
return arg
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
def cast_to_array(arg)
|
33
|
-
|
33
|
+
Array(arg)
|
34
|
+
end
|
35
|
+
|
36
|
+
def cast_to_hash(arg)
|
37
|
+
arg.is_a?(String) ? BW::JSON.parse(String(arg)) : arg
|
34
38
|
end
|
35
39
|
|
36
40
|
def cast_to_string(arg)
|
@@ -38,18 +42,19 @@ module MotionModel
|
|
38
42
|
end
|
39
43
|
|
40
44
|
def cast_to_type(column_name, arg) #nodoc
|
41
|
-
return nil if arg.nil? && ![ :boolean, :bool ].include?(
|
45
|
+
return nil if arg.nil? && ![ :boolean, :bool ].include?(column_type(column_name))
|
42
46
|
|
43
|
-
return case
|
44
|
-
when :string then cast_to_string(arg)
|
47
|
+
return case column_type(column_name)
|
48
|
+
when :string, :belongs_to_type then cast_to_string(arg)
|
45
49
|
when :boolean, :bool then cast_to_bool(arg)
|
46
50
|
when :int, :integer, :belongs_to_id then cast_to_integer(arg)
|
47
51
|
when :float, :double then cast_to_float(arg)
|
48
|
-
when :date, :time then cast_to_date(arg)
|
52
|
+
when :date, :time, :datetime then cast_to_date(arg)
|
49
53
|
when :text then cast_to_string(arg)
|
50
54
|
when :array then cast_to_array(arg)
|
55
|
+
when :hash then cast_to_hash(arg)
|
51
56
|
else
|
52
|
-
raise ArgumentError.new("type #{column_name} : #{
|
57
|
+
raise ArgumentError.new("type #{column_name} : #{column_type(column_name)} is not possible to cast.")
|
53
58
|
end
|
54
59
|
end
|
55
60
|
end
|
@@ -3,10 +3,8 @@ module MotionModel
|
|
3
3
|
class ValidationSpecificationError < RuntimeError; end
|
4
4
|
class RecordInvalid < RuntimeError; end
|
5
5
|
|
6
|
-
|
7
6
|
def self.included(base)
|
8
7
|
base.extend(ClassMethods)
|
9
|
-
base.instance_variable_set('@validations', [])
|
10
8
|
end
|
11
9
|
|
12
10
|
module ClassMethods
|
@@ -16,25 +14,39 @@ module MotionModel
|
|
16
14
|
raise ex
|
17
15
|
end
|
18
16
|
|
19
|
-
|
20
|
-
|
21
17
|
if validation_type == {}
|
22
18
|
ex = ValidationSpecificationError.new('validation type not present or not a hash')
|
23
19
|
raise ex
|
24
20
|
end
|
25
21
|
|
26
|
-
|
22
|
+
validations << {field => validation_type}
|
27
23
|
end
|
28
24
|
alias_method :validates, :validate
|
29
25
|
|
30
26
|
def validations
|
31
|
-
@validations
|
27
|
+
@validations ||= []
|
32
28
|
end
|
33
29
|
end
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
30
|
+
|
31
|
+
def do_save?(options = {})
|
32
|
+
_valid = true
|
33
|
+
if options[:validate] != false
|
34
|
+
call_hooks 'validation' do
|
35
|
+
_valid = valid?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
_valid
|
39
|
+
end
|
40
|
+
private :do_save?
|
41
|
+
|
42
|
+
def do_insert(options = {})
|
43
|
+
return false unless do_save?(options)
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def do_update(options = {})
|
48
|
+
return false unless do_save?(options)
|
49
|
+
super
|
38
50
|
end
|
39
51
|
|
40
52
|
# it fails loudly
|
@@ -49,10 +61,12 @@ module MotionModel
|
|
49
61
|
#
|
50
62
|
# * Second, it returns the result of performing the validations.
|
51
63
|
def valid?
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
64
|
+
call_hooks 'validation' do
|
65
|
+
@messages = []
|
66
|
+
@valid = true
|
67
|
+
self.class.validations.each do |validations|
|
68
|
+
validate_each(validations)
|
69
|
+
end
|
56
70
|
end
|
57
71
|
@valid
|
58
72
|
end
|
@@ -129,7 +143,7 @@ module MotionModel
|
|
129
143
|
elsif value.is_a?(String) || value.nil?
|
130
144
|
result = value.nil? || ((value.length == 0) == setting)
|
131
145
|
additional_message = setting ? "non-empty" : "non-empty"
|
132
|
-
add_message(field, "incorrect value supplied for #{field.to_s
|
146
|
+
add_message(field, "incorrect value supplied for #{field.to_s} -- should be #{additional_message}.") if result
|
133
147
|
return !result
|
134
148
|
end
|
135
149
|
return false
|
data/lib/motion_model/version.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
class ModelWithAdapter
|
2
|
+
include MotionModel::Model
|
3
|
+
include MotionModel::ArrayModelAdapter
|
4
|
+
|
5
|
+
columns :name
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'adapters with adapter method defined' do
|
9
|
+
it "does not raise an exception" do
|
10
|
+
lambda{ModelWithAdapter.create(:name => 'bob')}.should.not.raise
|
11
|
+
end
|
12
|
+
|
13
|
+
it "provides humanized string representation of the current adapter" do
|
14
|
+
ModelWithAdapter.create(:name => 'bob').adapter.should == 'Array Model Adapter'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class ModelWithoutAdapter
|
19
|
+
include MotionModel::Model
|
20
|
+
|
21
|
+
columns :name
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'adapters without adapter method defined' do
|
25
|
+
it "raises an exception" do
|
26
|
+
lambda{
|
27
|
+
ModelWithoutAdapter.new
|
28
|
+
}.should.raise(MotionModel::AdapterNotFoundError)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
class PersistTask
|
2
|
+
include MotionModel::Model
|
3
|
+
include MotionModel::ArrayModelAdapter
|
4
|
+
columns :name => :string,
|
5
|
+
:desc => :string,
|
6
|
+
:created_at => :date,
|
7
|
+
:updated_at => :date
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'persistence' do
|
11
|
+
before do
|
12
|
+
PersistTask.delete_all
|
13
|
+
%w(one two three).each do |task|
|
14
|
+
@tasks = PersistTask.create(:name => "name #{task}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "serializes data" do
|
19
|
+
lambda{PersistTask.serialize_to_file('test.dat')}.should.not.raise
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'reads persisted model data' do
|
23
|
+
PersistTask.serialize_to_file('test.dat')
|
24
|
+
|
25
|
+
PersistTask.delete_all
|
26
|
+
|
27
|
+
PersistTask.count.should == 0
|
28
|
+
|
29
|
+
tasks = PersistTask.deserialize_from_file('test.dat')
|
30
|
+
|
31
|
+
PersistTask.count.should == 3
|
32
|
+
PersistTask.first.name.should == 'name one'
|
33
|
+
PersistTask.last.name.should == 'name three'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "does not change created or updated date on load" do
|
37
|
+
created_at = PersistTask.first.created_at
|
38
|
+
updated_at = PersistTask.first.updated_at
|
39
|
+
|
40
|
+
PersistTask.serialize_to_file('test.dat')
|
41
|
+
tasks = PersistTask.deserialize_from_file('test.dat')
|
42
|
+
puts tasks.inspect
|
43
|
+
PersistTask.first.created_at.should == created_at
|
44
|
+
PersistTask.first.updated_at.should == updated_at
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'model change resiliency' do
|
48
|
+
it 'column addition' do
|
49
|
+
Object.send(:remove_const, :Foo) if defined?(Foo)
|
50
|
+
class Foo
|
51
|
+
include MotionModel::Model
|
52
|
+
include MotionModel::ArrayModelAdapter
|
53
|
+
columns :name => :string
|
54
|
+
end
|
55
|
+
@foo = Foo.create(:name=> 'Bob')
|
56
|
+
Foo.serialize_to_file('test.dat')
|
57
|
+
|
58
|
+
@foo.should.not.respond_to :address
|
59
|
+
|
60
|
+
Foo.delete_all
|
61
|
+
class Foo
|
62
|
+
columns :address => :string
|
63
|
+
end
|
64
|
+
Foo.deserialize_from_file('test.dat')
|
65
|
+
|
66
|
+
@foo = Foo.first
|
67
|
+
|
68
|
+
@foo.name.should == 'Bob'
|
69
|
+
@foo.address.should == nil
|
70
|
+
@foo.should.respond_to :address
|
71
|
+
Foo.length.should == 1
|
72
|
+
end
|
73
|
+
|
74
|
+
it "column removal" do
|
75
|
+
Object.send(:remove_const, :Foo) if defined?(Foo)
|
76
|
+
class Foo
|
77
|
+
include MotionModel::Model
|
78
|
+
include MotionModel::ArrayModelAdapter
|
79
|
+
columns :name => :string, :desc => :string
|
80
|
+
end
|
81
|
+
|
82
|
+
@foo = Foo.create(:name=> 'Bob', :desc => 'who cares anyway?')
|
83
|
+
Foo.serialize_to_file('test.dat')
|
84
|
+
|
85
|
+
@foo.should.respond_to :desc
|
86
|
+
|
87
|
+
Object.send(:remove_const, :Foo) if defined?(Foo)
|
88
|
+
class Foo
|
89
|
+
include MotionModel::Model
|
90
|
+
include MotionModel::ArrayModelAdapter
|
91
|
+
columns :name => :string,
|
92
|
+
:address => :string
|
93
|
+
end
|
94
|
+
Foo.deserialize_from_file('test.dat')
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "array model migrations" do
|
99
|
+
class TestForColumnAddition
|
100
|
+
include MotionModel::Model
|
101
|
+
include MotionModel::ArrayModelAdapter
|
102
|
+
columns :name => :string, :desc => :string
|
103
|
+
end
|
104
|
+
|
105
|
+
it "column addition should call migrate first as a test" do
|
106
|
+
TestForColumnAddition.mock!(:migrate)
|
107
|
+
TestForColumnAddition.deserialize_from_file('dfca.dat')
|
108
|
+
1.should == 1
|
109
|
+
end
|
110
|
+
|
111
|
+
it "this example should pass" do
|
112
|
+
1.should == 1
|
113
|
+
end
|
114
|
+
|
115
|
+
it "accepts properly formatted version strings" do
|
116
|
+
lambda{TestForColumnAddition.schema_version("3.1")}.should.not.raise
|
117
|
+
end
|
118
|
+
|
119
|
+
it "rejects non-string versions" do
|
120
|
+
lambda{TestForColumnAddition.schema_version(3)}.should.raise(MotionModel::ArrayModelAdapter::VersionNumberError)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "rejects improperly formated version strings" do
|
124
|
+
lambda{TestForColumnAddition.schema_version("3/1/1")}.should.raise(MotionModel::ArrayModelAdapter::VersionNumberError)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "returns the version number if no arguments supplied" do
|
128
|
+
TestForColumnAddition.schema_version("3.1")
|
129
|
+
TestForColumnAddition.schema_version.should == "3.1"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "remembering filename" do
|
134
|
+
class Foo
|
135
|
+
include MotionModel::Model
|
136
|
+
include MotionModel::ArrayModelAdapter
|
137
|
+
columns :name => :string
|
138
|
+
end
|
139
|
+
|
140
|
+
before do
|
141
|
+
Foo.delete_all
|
142
|
+
@foo = Foo.create(:name => 'Bob')
|
143
|
+
end
|
144
|
+
|
145
|
+
it "deserializes from last file if no filename given (previous method serialize)" do
|
146
|
+
Foo.serialize_to_file('test.dat')
|
147
|
+
Foo.delete_all
|
148
|
+
Foo.count.should == 0
|
149
|
+
Foo.deserialize_from_file
|
150
|
+
Foo.count.should == 1
|
151
|
+
end
|
152
|
+
|
153
|
+
it "deserializes from last file if no filename given (previous method deserialize)" do
|
154
|
+
Foo.serialize_to_file('test.dat')
|
155
|
+
Foo.serialize_to_file('bogus.dat') # serialize sets default filename to something bogus
|
156
|
+
File.delete Foo.documents_file('bogus.dat') # and we get rid of that file
|
157
|
+
Foo.deserialize_from_file('test.dat') # so we'll be sure the default filename last was set by deserialize
|
158
|
+
Foo.delete_all
|
159
|
+
Foo.count.should == 0
|
160
|
+
Foo.deserialize_from_file
|
161
|
+
Foo.count.should == 1
|
162
|
+
end
|
163
|
+
|
164
|
+
it "serializes to last file if no filename given (previous method serialize)" do
|
165
|
+
Foo.serialize_to_file('test.dat')
|
166
|
+
Foo.create(:name => 'Ted')
|
167
|
+
Foo.serialize_to_file
|
168
|
+
Foo.delete_all
|
169
|
+
Foo.count.should == 0
|
170
|
+
Foo.deserialize_from_file('test.dat')
|
171
|
+
Foo.count.should == 2
|
172
|
+
end
|
173
|
+
|
174
|
+
it "serializes to last file if no filename given (previous method deserialize)" do
|
175
|
+
Foo.serialize_to_file('test.dat')
|
176
|
+
Foo.delete_all
|
177
|
+
Foo.serialize_to_file('bogus.dat') # serialize sets default filename to something bogus
|
178
|
+
File.delete Foo.documents_file('bogus.dat') # and we get rid of that file
|
179
|
+
Foo.deserialize_from_file('test.dat') # so we'll be sure the default filename was last set by deserialize
|
180
|
+
Foo.create(:name => 'Ted')
|
181
|
+
Foo.serialize_to_file
|
182
|
+
Foo.delete_all
|
183
|
+
Foo.count.should == 0
|
184
|
+
Foo.deserialize_from_file('test.dat')
|
185
|
+
Foo.count.should == 2
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class Parent
|
192
|
+
include MotionModel::Model
|
193
|
+
include MotionModel::ArrayModelAdapter
|
194
|
+
columns :name
|
195
|
+
has_many :children
|
196
|
+
end
|
197
|
+
|
198
|
+
class Child
|
199
|
+
include MotionModel::Model
|
200
|
+
include MotionModel::ArrayModelAdapter
|
201
|
+
columns :name
|
202
|
+
belongs_to :parent
|
203
|
+
end
|
204
|
+
|
205
|
+
describe "serialization of relations" do
|
206
|
+
before do
|
207
|
+
parent = Parent.create(:name => 'BoB')
|
208
|
+
parent.children_relation.create :name => 'Fergie'
|
209
|
+
parent.children_relation.create :name => 'Will I Am'
|
210
|
+
end
|
211
|
+
|
212
|
+
it "is wired up right" do
|
213
|
+
Parent.first.name.should == 'BoB'
|
214
|
+
Parent.first.children.count.should == 2
|
215
|
+
end
|
216
|
+
|
217
|
+
it "serializes and deserializes properly" do
|
218
|
+
Parent.serialize_to_file('parents.dat')
|
219
|
+
Child.serialize_to_file('children.dat')
|
220
|
+
Parent.delete_all
|
221
|
+
Child.delete_all
|
222
|
+
Parent.deserialize_from_file('parents.dat')
|
223
|
+
Child.deserialize_from_file('children.dat')
|
224
|
+
Parent.first.name.should == 'BoB'
|
225
|
+
Parent.first.children.count.should == 2
|
226
|
+
Parent.first.children.first.name.should == 'Fergie'
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
@@ -58,8 +58,8 @@ describe "cascading deletes" do
|
|
58
58
|
|
59
59
|
it "deletes assignees that belong to a destroyed task" do
|
60
60
|
task = CascadingTask.create(:name => 'cascading')
|
61
|
-
task.
|
62
|
-
task.
|
61
|
+
task.cascaded_assignees_relation.create(:assignee_name => 'joe')
|
62
|
+
task.cascaded_assignees_relation.create(:assignee_name => 'bill')
|
63
63
|
|
64
64
|
CascadingTask.count.should == 1
|
65
65
|
CascadedAssignee.count.should == 2
|
@@ -74,7 +74,7 @@ describe "cascading deletes" do
|
|
74
74
|
1.upto(3) do |item|
|
75
75
|
task = CascadingTask.create :name => "Task #{item}"
|
76
76
|
1.upto(3) do |assignee|
|
77
|
-
task.
|
77
|
+
task.cascaded_assignees_relation.create :assignee_name => "assignee #{assignee} for task #{task}"
|
78
78
|
end
|
79
79
|
end
|
80
80
|
CascadingTask.count.should == 3
|
@@ -88,8 +88,8 @@ describe "cascading deletes" do
|
|
88
88
|
|
89
89
|
it "deletes only one level when a task is destroyed but dependent is delete" do
|
90
90
|
task = CascadingTask.create :name => 'dependent => :delete'
|
91
|
-
assignee = task.
|
92
|
-
assignee.
|
91
|
+
assignee = task.cascaded_assignees_relation.create :assignee_name => 'deletable assignee'
|
92
|
+
assignee.employees_relation.create :name => 'person who sticks around'
|
93
93
|
|
94
94
|
CascadingTask.count.should == 1
|
95
95
|
CascadedAssignee.count.should == 1
|
data/spec/date_spec.rb
CHANGED
@@ -11,7 +11,8 @@ describe "time conversions" do
|
|
11
11
|
should == "03-18-2012 | 07:00 PM"
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
describe "auto_date_fields" do
|
15
|
+
|
15
16
|
class Creatable
|
16
17
|
include MotionModel::Model
|
17
18
|
include MotionModel::ArrayModelAdapter
|
@@ -19,25 +20,37 @@ describe "time conversions" do
|
|
19
20
|
:created_at => :date
|
20
21
|
end
|
21
22
|
|
22
|
-
c = Creatable.new(:name => 'test')
|
23
|
-
lambda{c.save}.should.change{c.created_at}
|
24
|
-
end
|
25
|
-
|
26
|
-
it "Sets updated_at when an item is created" do
|
27
23
|
class Updateable
|
28
24
|
include MotionModel::Model
|
29
25
|
include MotionModel::ArrayModelAdapter
|
30
26
|
columns :name => :string,
|
31
|
-
:created_at => :date,
|
32
27
|
:updated_at => :date
|
33
28
|
end
|
34
29
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
30
|
+
it "Sets created_at when an item is created" do
|
31
|
+
c = Creatable.new(:name => 'test')
|
32
|
+
lambda{c.save}.should.change{c.created_at}
|
33
|
+
end
|
34
|
+
|
35
|
+
it "Sets updated_at when an item is created" do
|
36
|
+
c = Updateable.new(:name => 'test')
|
37
|
+
lambda{c.save}.should.change{c.updated_at}
|
38
|
+
end
|
39
|
+
|
40
|
+
it "Doesn't update created_at when an item is updated" do
|
41
|
+
c = Creatable.create(:name => 'test')
|
42
|
+
c.name = 'test 1'
|
43
|
+
lambda{c.save}.should.not.change{c.created_at}
|
44
|
+
end
|
45
|
+
|
46
|
+
it "Updates updated_at when an item is updated" do
|
47
|
+
c = Updateable.create(:name => 'test')
|
48
|
+
sleep 1
|
49
|
+
c.name = 'test 1'
|
50
|
+
lambda{ c.save }.should.change{c.updated_at}
|
51
|
+
end
|
52
|
+
|
41
53
|
end
|
42
54
|
|
55
|
+
|
43
56
|
end
|
data/spec/finder_spec.rb
CHANGED
data/spec/formotion_spec.rb
CHANGED
data/spec/model_hook_spec.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
Object.send(:remove_const, :Task) if defined?(Task)
|
1
2
|
class Task
|
2
3
|
attr_reader :before_delete_called, :after_delete_called
|
3
4
|
attr_reader :before_save_called, :after_save_called
|
@@ -8,19 +9,19 @@ class Task
|
|
8
9
|
:details => :string,
|
9
10
|
:some_day => :date
|
10
11
|
|
11
|
-
def before_delete(
|
12
|
+
def before_delete(sender)
|
12
13
|
@before_delete_called = true
|
13
14
|
end
|
14
15
|
|
15
|
-
def after_delete(
|
16
|
+
def after_delete(sender)
|
16
17
|
@after_delete_called = true
|
17
18
|
end
|
18
19
|
|
19
|
-
def before_save(
|
20
|
+
def before_save(sender)
|
20
21
|
@before_save_called = true
|
21
22
|
end
|
22
23
|
|
23
|
-
def after_save(
|
24
|
+
def after_save(sender)
|
24
25
|
@after_save_called = true
|
25
26
|
end
|
26
27
|
|
data/spec/model_spec.rb
CHANGED
@@ -45,12 +45,6 @@ describe "Creating a model" do
|
|
45
45
|
atask.should.respond_to(:details)
|
46
46
|
end
|
47
47
|
|
48
|
-
it 'simply bypasses spurious attributes erroneously set' do
|
49
|
-
a_task = Task.new(:name => 'details', :zoo => 'very bad')
|
50
|
-
a_task.should.not.respond_to(:zoo)
|
51
|
-
a_task.name.should.equal('details')
|
52
|
-
end
|
53
|
-
|
54
48
|
it "adds a default value if none supplied" do
|
55
49
|
a_type_test = TypeCast.new
|
56
50
|
a_type_test.an_int.should.equal(3)
|
@@ -89,7 +83,7 @@ describe "Creating a model" do
|
|
89
83
|
end
|
90
84
|
|
91
85
|
it "the type of a column can be retrieved" do
|
92
|
-
Task.new.
|
86
|
+
Task.new.column_type(:some_day).should.equal(:date)
|
93
87
|
end
|
94
88
|
|
95
89
|
end
|
data/spec/relation_spec.rb
CHANGED
@@ -30,9 +30,6 @@ class EmailAccount
|
|
30
30
|
belongs_to :user
|
31
31
|
end
|
32
32
|
|
33
|
-
Inflector.inflections.irregular 'assignees', 'assignee'
|
34
|
-
Inflector.inflections.irregular 'assignee', 'assignees'
|
35
|
-
|
36
33
|
describe 'related objects' do
|
37
34
|
describe "supporting belongs_to and has_many with camelcased relations" do
|
38
35
|
before do
|
@@ -42,7 +39,7 @@ describe 'related objects' do
|
|
42
39
|
|
43
40
|
it "camelcased style" do
|
44
41
|
t = User.create(:name => "Arkan")
|
45
|
-
t.
|
42
|
+
t.email_accounts_relation.create(:name => "Gmail")
|
46
43
|
EmailAccount.first.user.name.should == "Arkan"
|
47
44
|
User.last.email_accounts.last.name.should == "Gmail"
|
48
45
|
end
|
@@ -61,12 +58,12 @@ describe 'related objects' do
|
|
61
58
|
|
62
59
|
it 'relation objects are empty on initialization' do
|
63
60
|
a_task = Task.create
|
64
|
-
a_task.
|
61
|
+
a_task.assignees_relation.all.should.be.empty
|
65
62
|
end
|
66
63
|
|
67
64
|
it "supports creating related objects directly on parents" do
|
68
65
|
a_task = Task.create(:name => 'Walk the Dog')
|
69
|
-
a_task.
|
66
|
+
a_task.assignees_relation.create(:assignee_name => 'bob')
|
70
67
|
a_task.assignees.count.should == 1
|
71
68
|
a_task.assignees.first.assignee_name.should == 'bob'
|
72
69
|
Assignee.count.should == 1
|
@@ -84,7 +81,7 @@ describe 'related objects' do
|
|
84
81
|
assignee_index = 1
|
85
82
|
@tasks << t
|
86
83
|
1.upto(task * 2) do |assignee|
|
87
|
-
@assignees << t.
|
84
|
+
@assignees << t.assignees_relation.create(:assignee_name => "employee #{assignee_index}_assignee_for_task_#{t.id}")
|
88
85
|
assignee_index += 1
|
89
86
|
end
|
90
87
|
end
|
@@ -108,7 +105,7 @@ describe 'related objects' do
|
|
108
105
|
assignee = Assignee.new(:assignee_name => 'Zoe')
|
109
106
|
Task.count.should == 3
|
110
107
|
assignee_count = Task.find(3).assignees.count
|
111
|
-
Task.find(3).
|
108
|
+
Task.find(3).assignees_relation.push(assignee)
|
112
109
|
Task.find(3).assignees.count.should == assignee_count + 1
|
113
110
|
end
|
114
111
|
|
@@ -116,7 +113,7 @@ describe 'related objects' do
|
|
116
113
|
|
117
114
|
it "supports creating blank (empty) scratchpad associated objects" do
|
118
115
|
task = Task.create :name => 'watch a movie'
|
119
|
-
assignee = task.
|
116
|
+
assignee = task.assignees_relation.new # TODO per Rails convention, this should really be #build, not #new
|
120
117
|
assignee.assignee_name = 'Chloe'
|
121
118
|
assignee.save
|
122
119
|
task.assignees.count.should == 1
|
@@ -132,7 +129,7 @@ describe 'related objects' do
|
|
132
129
|
|
133
130
|
it "allows a child to back-reference its parent" do
|
134
131
|
t = Task.create(:name => "Walk the Dog")
|
135
|
-
t.
|
132
|
+
t.assignees_relation.create(:assignee_name => "Rihanna")
|
136
133
|
Assignee.first.task.name.should == "Walk the Dog"
|
137
134
|
end
|
138
135
|
|
@@ -146,7 +143,7 @@ describe 'related objects' do
|
|
146
143
|
|
147
144
|
describe "basic wiring" do
|
148
145
|
before do
|
149
|
-
@t1.
|
146
|
+
@t1.assignees_relation << @a1
|
150
147
|
end
|
151
148
|
|
152
149
|
it "pushing a created assignee gives a task count of 1" do
|
@@ -164,7 +161,7 @@ describe 'related objects' do
|
|
164
161
|
|
165
162
|
describe "when pushing assignees onto two different tasks" do
|
166
163
|
before do
|
167
|
-
@t2.
|
164
|
+
@t2.assignees_relation << @a1
|
168
165
|
end
|
169
166
|
|
170
167
|
it "pushing assignees to two different tasks lets the last task have the assignee (count)" do
|
@@ -186,7 +183,7 @@ describe 'related objects' do
|
|
186
183
|
|
187
184
|
describe "directly assigning to child" do
|
188
185
|
it "directly assigning a different task to an assignee changes the assignee's task" do
|
189
|
-
@a1.
|
186
|
+
@a1.task_id = @t1.id
|
190
187
|
@a1.save
|
191
188
|
@t1.assignees.count.should == 1
|
192
189
|
@t1.assignees.first.assignee_name.should == @a1.assignee_name
|