motion_model 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/CHANGELOG
CHANGED
@@ -1,10 +1,21 @@
|
|
1
|
-
|
1
|
+
2013-04-13: WARNING: Possible breaking change. Hook methods changed to send
|
2
|
+
affected object. So, if you have:
|
3
|
+
|
4
|
+
def after_create
|
5
|
+
|
6
|
+
You will get an error about expecting 1 argument, got 0
|
7
|
+
|
8
|
+
The correct signature is:
|
9
|
+
|
10
|
+
def after_create(sender)
|
11
|
+
|
12
|
+
2013-03-15: Fixed bug where created_at and updated_at were being incorrectly
|
2
13
|
when restored from persistence (thanks Justin McPherson for finding
|
3
14
|
that).
|
4
15
|
|
5
16
|
Moved all NSCoder stuff out of Model to ArrayModelAdapter.
|
6
17
|
|
7
|
-
|
18
|
+
2013-02-19: Included Doug Puchalski's great refactoring of Model that provides an
|
8
19
|
adapter for ArrayModelAdapter. WARNING!!! This is a breaking change
|
9
20
|
since version 0.3.8. You will have to include:
|
10
21
|
|
@@ -14,9 +25,9 @@
|
|
14
25
|
Failure to include an adapter (note: spelling counts :) will result
|
15
26
|
in an exception so this will not quietly fail.
|
16
27
|
|
17
|
-
|
28
|
+
2013-01-24: Added block-structured transactions.
|
18
29
|
|
19
|
-
|
30
|
+
2013-01-14: Fixed problem where data returned from forms was of type NSString, which
|
20
31
|
confused some monkey-patching code.
|
21
32
|
Changed before_ hooks such that handlers returning false would terminate
|
22
33
|
the process. So, if before_save returns anything other than false, the
|
@@ -24,7 +35,7 @@
|
|
24
35
|
interrupted.
|
25
36
|
Fixed immutable string issue in validations.
|
26
37
|
|
27
|
-
|
38
|
+
2013-01-09: Added automatic date/timestamp support for created_at and updated_at columns
|
28
39
|
Added Hash extension except, Array introspection methods has_hash_key? and
|
29
40
|
has_hash_value?
|
30
41
|
Commit of Formotion module including optional inclusion/suppression of the
|
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -97,6 +97,7 @@ You can define your models and their schemas in Ruby. For example:
|
|
97
97
|
```ruby
|
98
98
|
class Task
|
99
99
|
include MotionModel::Model
|
100
|
+
include MotionModel::ArrayModelAdapter
|
100
101
|
|
101
102
|
columns :name => :string,
|
102
103
|
:description => :string,
|
@@ -117,6 +118,7 @@ Models support default values, so if you specify your model like this, you get d
|
|
117
118
|
```ruby
|
118
119
|
class Task
|
119
120
|
include MotionModel::Model
|
121
|
+
include MotionModel::ArrayModelAdapter
|
120
122
|
|
121
123
|
columns :name => :string,
|
122
124
|
:due_date => {:type => :date, :default => '2012-09-15'}
|
@@ -128,6 +130,7 @@ You can also include the `Validatable` module to get field validation. For examp
|
|
128
130
|
```ruby
|
129
131
|
class Task
|
130
132
|
include MotionModel::Model
|
133
|
+
include MotionModel::ArrayModelAdapter
|
131
134
|
include MotionModel::Validatable
|
132
135
|
|
133
136
|
columns :name => :string,
|
@@ -188,6 +191,7 @@ To use validations in your model, declare your model as follows:
|
|
188
191
|
```ruby
|
189
192
|
class MyValidatableModel
|
190
193
|
include MotionModel::Model
|
194
|
+
include MotionModel::ArrayModelAdapter
|
191
195
|
include MotionModel::Validatable
|
192
196
|
|
193
197
|
# All other model-y stuff here
|
@@ -317,12 +321,14 @@ Using MotionModel
|
|
317
321
|
```ruby
|
318
322
|
class Task
|
319
323
|
include MotionModel::Model
|
324
|
+
include MotionModel::ArrayModelAdapter
|
320
325
|
columns :name => :string
|
321
326
|
has_many :assignees
|
322
327
|
end
|
323
328
|
|
324
329
|
class Assignee
|
325
330
|
include MotionModel::Model
|
331
|
+
include MotionModel::ArrayModelAdapter
|
326
332
|
columns :assignee_name => :string
|
327
333
|
belongs_to :task
|
328
334
|
end
|
@@ -361,6 +367,7 @@ The key to how the `destroy` variants work in how the relation is declared. You
|
|
361
367
|
```ruby
|
362
368
|
class Task
|
363
369
|
include MotionModel::Model
|
370
|
+
include MotionModel::ArrayModelAdapter
|
364
371
|
columns :name => :string
|
365
372
|
has_many :assignees
|
366
373
|
end
|
@@ -388,6 +395,43 @@ related to the assignees remains intact.
|
|
388
395
|
|
389
396
|
Note: This syntax is modeled on the Rails `:dependent => :destroy` options in `ActiveRecord`.
|
390
397
|
|
398
|
+
## Hook Methods
|
399
|
+
|
400
|
+
During a save or delete operation, hook methods are called to allow you a chance to modify the
|
401
|
+
object at that point. These hook methods are:
|
402
|
+
|
403
|
+
```ruby
|
404
|
+
before_save(sender)
|
405
|
+
after_save(sender)
|
406
|
+
before_delete(sender)
|
407
|
+
after_delete(sender)
|
408
|
+
```
|
409
|
+
|
410
|
+
MotionModel makes no distinction between destroy and delete when calling hook methods, as it only
|
411
|
+
calls them when the actual object is deleted. In a destroy operation, during the cascading delete,
|
412
|
+
the delete hooks are called (again) at the point of object deletion.
|
413
|
+
|
414
|
+
Note that the method signatures may be different from previous implementations. No longer can you
|
415
|
+
declare a hook method without the `sender` argument.
|
416
|
+
|
417
|
+
Finally, contrasting hook methods with notifications, the hook methods `before_save` and `after_save`
|
418
|
+
are called before the save operation begins and after it completes. However, the notification (covered
|
419
|
+
below) is only issued after the save operation. However... the notification understands whether the
|
420
|
+
operation was a save or update. Rule of thumb: If you want to catch an operation before it begins,
|
421
|
+
use the hook. If you just want to know about it when it happens, use the notification.
|
422
|
+
|
423
|
+
The delete hooks happen around the delete operation and, again, allow you the option to mess with the
|
424
|
+
object before you allow the process to go forward (pretty much, the `before_delete` hook does this).
|
425
|
+
|
426
|
+
*IMPORTANT*: Returning false in a before hook stops the rest of the operation. So, for example, you
|
427
|
+
could prevent the deletion of the last admin by writing something like this:
|
428
|
+
|
429
|
+
```ruby
|
430
|
+
def before_delete(sender)
|
431
|
+
return false if sender.find(:privilege_level).eq('admin').count < 2
|
432
|
+
end
|
433
|
+
```
|
434
|
+
|
391
435
|
## Transactions and Undo/Cancel
|
392
436
|
|
393
437
|
MotionModel is not ActiveRecord. MotionModel is not a database-backed mapper. The bottom line is that when you change a field in a model, even if you don't save it, you are partying on the central object store. In part, this is because Ruby copies objects by reference, so when you do a find, you get a reference to the object *in the central object store*.
|
@@ -460,12 +504,14 @@ end
|
|
460
504
|
```ruby
|
461
505
|
class Task
|
462
506
|
include MotionModel::Model
|
507
|
+
include MotionModel::ArrayModelAdapter
|
463
508
|
has_many :assignees
|
464
509
|
# etc
|
465
510
|
end
|
466
511
|
|
467
512
|
class Assignee
|
468
513
|
include MotionModel::Model
|
514
|
+
include MotionModel::ArrayModelAdapter
|
469
515
|
belongs_to :task
|
470
516
|
# etc
|
471
517
|
end
|
data/Rakefile
CHANGED
@@ -2,10 +2,13 @@
|
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
$:.unshift("/Library/RubyMotion/lib")
|
4
4
|
require 'motion/project'
|
5
|
+
require 'bundler'
|
6
|
+
Bundler.require
|
5
7
|
|
6
8
|
Motion::Project::App.setup do |app|
|
7
9
|
# Use `rake config' to see complete project settings.
|
10
|
+
app.name = 'MotionModel'
|
8
11
|
app.delegate_class = 'FakeDelegate'
|
9
|
-
app.files = Dir.glob('./lib/motion_model/**/*.rb')
|
12
|
+
app.files = Dir.glob('./lib/motion_model/**/*.rb') + app.files
|
10
13
|
app.files = (app.files + Dir.glob('./app/**/*.rb')).uniq
|
11
14
|
end
|
@@ -7,18 +7,20 @@ module MotionModel
|
|
7
7
|
def self.included(base)
|
8
8
|
base.extend(PrivateClassMethods)
|
9
9
|
base.extend(PublicClassMethods)
|
10
|
-
base.
|
10
|
+
base.instance_eval do
|
11
|
+
_reset_next_id
|
12
|
+
end
|
11
13
|
end
|
12
14
|
|
13
15
|
module PublicClassMethods
|
14
|
-
|
15
|
-
def collection
|
16
|
+
def collection
|
16
17
|
@collection ||= []
|
17
18
|
end
|
18
19
|
|
19
20
|
def insert(object)
|
20
21
|
collection << object
|
21
22
|
end
|
23
|
+
alias :<< :insert
|
22
24
|
|
23
25
|
def length
|
24
26
|
collection.length
|
@@ -32,11 +34,8 @@ module MotionModel
|
|
32
34
|
# Do each delete so any on_delete and
|
33
35
|
# cascades are called, then empty the
|
34
36
|
# collection and compact the array.
|
35
|
-
bulk_update
|
36
|
-
|
37
|
-
end
|
38
|
-
@collection = []
|
39
|
-
@_next_id = 1
|
37
|
+
bulk_update { collection.pop.delete until collection.empty? }
|
38
|
+
_reset_next_id
|
40
39
|
end
|
41
40
|
|
42
41
|
# Finds row(s) within the data store. E.g.,
|
@@ -59,36 +58,40 @@ module MotionModel
|
|
59
58
|
return collection.select{|element| element.id == target_id}.first
|
60
59
|
end
|
61
60
|
|
62
|
-
ArrayFinderQuery.new(args[0].to_sym,
|
61
|
+
ArrayFinderQuery.new(args[0].to_sym, collection)
|
63
62
|
end
|
64
63
|
alias_method :where, :find
|
65
64
|
|
65
|
+
def find_by_id(id)
|
66
|
+
find(:id).eq(id).first
|
67
|
+
end
|
68
|
+
|
66
69
|
# Returns query result as an array
|
67
70
|
def all
|
68
71
|
collection
|
69
72
|
end
|
70
73
|
|
71
74
|
def order(field_name = nil, &block)
|
72
|
-
ArrayFinderQuery.new(
|
75
|
+
ArrayFinderQuery.new(collection).order(field_name, &block)
|
73
76
|
end
|
74
77
|
|
75
78
|
end
|
76
79
|
|
77
80
|
module PrivateClassMethods
|
81
|
+
private
|
78
82
|
|
79
83
|
# Returns next available id
|
80
|
-
def
|
84
|
+
def _next_id #nodoc
|
81
85
|
@_next_id
|
82
86
|
end
|
83
87
|
|
84
|
-
|
85
|
-
|
86
|
-
@_next_id = value
|
88
|
+
def _reset_next_id
|
89
|
+
@_next_id = 1
|
87
90
|
end
|
88
91
|
|
89
92
|
# Increments next available id
|
90
|
-
def
|
91
|
-
@_next_id
|
93
|
+
def increment_next_id(other_id) #nodoc
|
94
|
+
@_next_id = [@_next_id, other_id.to_i].max + 1
|
92
95
|
end
|
93
96
|
|
94
97
|
end
|
@@ -97,6 +100,10 @@ module MotionModel
|
|
97
100
|
assign_id(options)
|
98
101
|
end
|
99
102
|
|
103
|
+
def increment_next_id(other_id)
|
104
|
+
self.class.send(:increment_next_id, other_id)
|
105
|
+
end
|
106
|
+
|
100
107
|
# Undelete does pretty much as its name implies. However,
|
101
108
|
# the natural sort order is not preserved. IMPORTANT: If
|
102
109
|
# you are trying to undo a cascading delete, this will not
|
@@ -104,7 +111,11 @@ module MotionModel
|
|
104
111
|
|
105
112
|
def undelete
|
106
113
|
collection << self
|
107
|
-
|
114
|
+
issue_notification(:action => 'add')
|
115
|
+
end
|
116
|
+
|
117
|
+
def collection #nodoc
|
118
|
+
self.class.collection
|
108
119
|
end
|
109
120
|
|
110
121
|
# This adds to the ArrayStore without the magic date
|
@@ -122,32 +133,45 @@ module MotionModel
|
|
122
133
|
end
|
123
134
|
alias_method :count, :length
|
124
135
|
|
136
|
+
def rebuild_relation_for(name, instance_or_collection) # nodoc
|
137
|
+
end
|
138
|
+
|
125
139
|
private
|
126
140
|
|
141
|
+
def _next_id
|
142
|
+
self.class.send(:_next_id)
|
143
|
+
end
|
144
|
+
|
127
145
|
def assign_id(options) #nodoc
|
128
|
-
|
129
|
-
|
130
|
-
else
|
131
|
-
self.class.next_id = [options[:id].to_i, self.class.next_id].max
|
132
|
-
end
|
133
|
-
self.class.increment_id
|
146
|
+
options[:id] ||= _next_id
|
147
|
+
increment_next_id(options[:id])
|
134
148
|
end
|
135
149
|
|
136
|
-
def
|
137
|
-
|
150
|
+
def relation_for(col) # nodoc
|
151
|
+
col = column_named(col)
|
152
|
+
related_klass = col.classify
|
153
|
+
|
154
|
+
case col.type
|
155
|
+
when :belongs_to
|
156
|
+
related_klass.find(@data[:id])
|
157
|
+
when :has_many
|
158
|
+
related_klass.find(generate_belongs_to_id(self.class)).belongs_to(self, related_klass).eq(@data[:id])
|
159
|
+
else
|
160
|
+
nil
|
161
|
+
end
|
138
162
|
end
|
139
163
|
|
140
|
-
def do_insert
|
164
|
+
def do_insert(options = {})
|
141
165
|
collection << self
|
142
166
|
end
|
143
167
|
|
144
|
-
def do_update
|
168
|
+
def do_update(options = {})
|
145
169
|
end
|
146
170
|
|
147
171
|
def do_delete
|
148
172
|
target_index = collection.index{|item| item.id == self.id}
|
149
173
|
collection.delete_at(target_index) unless target_index.nil?
|
150
|
-
|
174
|
+
issue_notification(:action => 'delete')
|
151
175
|
end
|
152
176
|
|
153
177
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module MotionModel
|
2
|
+
module ArrayModelAdapter
|
3
|
+
class PersistFileError < Exception; end
|
4
|
+
class VersionNumberError < ArgumentError; end
|
5
|
+
|
6
|
+
module PublicClassMethods
|
7
|
+
|
8
|
+
def validate_schema_version(version_number)
|
9
|
+
raise MotionModel::ArrayModelAdapter::VersionNumberError.new('version number must be a string') unless version_number.is_a?(String)
|
10
|
+
if version_number !~ /^[\d.]+$/
|
11
|
+
raise MotionModel::ArrayModelAdapter::VersionNumberError.new('version number string must contain only numbers and periods')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Declare a version number for this schema. For example:
|
16
|
+
#
|
17
|
+
# class Task
|
18
|
+
# include MotionModel::Model
|
19
|
+
# include MotionModel::ArrayModelAdapter
|
20
|
+
#
|
21
|
+
# version_number 1.0.1
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# When a version number mismatch occurs as an individual row is loaded
|
25
|
+
# from persistent storage, the migrate method is invoked, allowing
|
26
|
+
# you to programmatically migrate on a per-row basis.
|
27
|
+
|
28
|
+
def schema_version(*version_number)
|
29
|
+
if version_number.empty?
|
30
|
+
return @schema_version
|
31
|
+
else
|
32
|
+
validate_schema_version(version_number[0])
|
33
|
+
@schema_version = version_number[0]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def migrate
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# Returns the unarchived object if successful, otherwise false
|
42
|
+
#
|
43
|
+
# Note that subsequent calls to serialize/deserialize methods
|
44
|
+
# will remember the file name, so they may omit that argument.
|
45
|
+
#
|
46
|
+
# Raises a +MotionModel::PersistFileFailureError+ on failure.
|
47
|
+
def deserialize_from_file(file_name = nil)
|
48
|
+
if schema_version != '1.0.0'
|
49
|
+
migrate
|
50
|
+
end
|
51
|
+
|
52
|
+
@file_name = file_name if file_name
|
53
|
+
|
54
|
+
if File.exist? documents_file(@file_name)
|
55
|
+
error_ptr = Pointer.new(:object)
|
56
|
+
|
57
|
+
data = NSData.dataWithContentsOfFile(documents_file(@file_name), options:NSDataReadingMappedIfSafe, error:error_ptr)
|
58
|
+
|
59
|
+
if data.nil?
|
60
|
+
error = error_ptr[0]
|
61
|
+
raise MotionModel::PersistFileError.new "Error when reading the data: #{error}"
|
62
|
+
else
|
63
|
+
bulk_update do
|
64
|
+
NSKeyedUnarchiver.unarchiveObjectWithData(data)
|
65
|
+
end
|
66
|
+
return self
|
67
|
+
end
|
68
|
+
else
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
# Serializes data to a persistent store (file, in this
|
73
|
+
# terminology). Serialization is synchronous, so this
|
74
|
+
# will pause your run loop until complete.
|
75
|
+
#
|
76
|
+
# +file_name+ is the name of the persistent store you
|
77
|
+
# want to use. If you omit this, it will use the last
|
78
|
+
# remembered file name.
|
79
|
+
#
|
80
|
+
# Raises a +MotionModel::PersistFileError+ on failure.
|
81
|
+
def serialize_to_file(file_name = nil)
|
82
|
+
@file_name = file_name if file_name
|
83
|
+
error_ptr = Pointer.new(:object)
|
84
|
+
|
85
|
+
data = NSKeyedArchiver.archivedDataWithRootObject collection
|
86
|
+
unless data.writeToFile(documents_file(@file_name), options: NSDataWritingAtomic, error: error_ptr)
|
87
|
+
# De-reference the pointer.
|
88
|
+
error = error_ptr[0]
|
89
|
+
|
90
|
+
# Now we can use the `error' object.
|
91
|
+
raise MotionModel::PersistFileError.new "Error when writing data: #{error}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def documents_file(file_name)
|
97
|
+
file_path = File.join NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true), file_name
|
98
|
+
file_path
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def initWithCoder(coder)
|
103
|
+
self.init
|
104
|
+
|
105
|
+
new_tag_id = 1
|
106
|
+
columns.each do |attr|
|
107
|
+
next if has_relation?(attr)
|
108
|
+
# If a model revision has taken place, don't try to decode
|
109
|
+
# something that's not there.
|
110
|
+
if coder.containsValueForKey(attr.to_s)
|
111
|
+
value = coder.decodeObjectForKey(attr.to_s)
|
112
|
+
self.send("#{attr}=", value)
|
113
|
+
else
|
114
|
+
self.send("#{attr}=", nil)
|
115
|
+
end
|
116
|
+
|
117
|
+
# re-issue tags to make sure they are unique
|
118
|
+
@tag = new_tag_id
|
119
|
+
new_tag_id += 1
|
120
|
+
end
|
121
|
+
add_to_store
|
122
|
+
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
# Follow Apple's recommendation not to encode missing
|
127
|
+
# values.
|
128
|
+
def encodeWithCoder(coder)
|
129
|
+
columns.each do |attr|
|
130
|
+
# Serialize attributes except the proxy has_many and belongs_to ones.
|
131
|
+
unless [:belongs_to, :has_many].include? column_named(attr).type
|
132
|
+
value = self.send(attr)
|
133
|
+
unless value.nil?
|
134
|
+
coder.encodeObject(value, forKey: attr.to_s)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
data/lib/motion_model/ext.rb
CHANGED
@@ -351,3 +351,20 @@ def UIInterfaceOrientationIsPortrait(orientation)
|
|
351
351
|
orientation == UIInterfaceOrientationPortrait ||
|
352
352
|
orientation == UIInterfaceOrientationPortraitUpsideDown
|
353
353
|
end
|
354
|
+
|
355
|
+
class Module
|
356
|
+
# Retrieve a constant within its scope
|
357
|
+
def deep_const_get(const)
|
358
|
+
if Symbol === const
|
359
|
+
const = const.to_s
|
360
|
+
else
|
361
|
+
const = const.to_str.dup
|
362
|
+
end
|
363
|
+
if const.sub!(/^::/, '')
|
364
|
+
base = Object
|
365
|
+
else
|
366
|
+
base = self
|
367
|
+
end
|
368
|
+
const.split(/::/).inject(base) { |mod, name| mod.const_get(name) }
|
369
|
+
end
|
370
|
+
end
|
@@ -161,9 +161,14 @@ module MotionModel
|
|
161
161
|
|
162
162
|
# returns all elements that match as an array.
|
163
163
|
def all
|
164
|
-
|
164
|
+
to_a
|
165
165
|
end
|
166
166
|
|
167
|
+
# returns all elements that match as an array.
|
168
|
+
def to_a
|
169
|
+
@collection
|
170
|
+
end
|
171
|
+
|
167
172
|
# each is a shortcut method to turn a query into an iterator. It allows
|
168
173
|
# you to write code like:
|
169
174
|
#
|
@@ -4,14 +4,14 @@ module MotionModel
|
|
4
4
|
attr_accessor :name
|
5
5
|
attr_accessor :type
|
6
6
|
attr_accessor :default
|
7
|
-
attr_accessor :
|
7
|
+
attr_accessor :dependent
|
8
8
|
|
9
9
|
def initialize(name = nil, type = nil, options = {})
|
10
10
|
@name = name
|
11
11
|
@type = type
|
12
12
|
raise RuntimeError.new "columns need a type declared." if type.nil?
|
13
13
|
@default = options.delete :default
|
14
|
-
@
|
14
|
+
@dependent = options.delete :dependent
|
15
15
|
@options = options
|
16
16
|
end
|
17
17
|
|
@@ -19,16 +19,32 @@ module MotionModel
|
|
19
19
|
@options
|
20
20
|
end
|
21
21
|
|
22
|
+
def class_name
|
23
|
+
@options[:joined_class_name] || @name
|
24
|
+
end
|
25
|
+
|
22
26
|
def classify
|
23
|
-
|
24
|
-
|
25
|
-
@klass ||= Object.const_get(@name.to_s.camelize)
|
26
|
-
when :has_many
|
27
|
-
@klass ||= Object.const_get(@name.to_s.singularize.camelize)
|
27
|
+
if @options[:class]
|
28
|
+
@options[:class]
|
28
29
|
else
|
29
|
-
|
30
|
+
case @type
|
31
|
+
when :belongs_to
|
32
|
+
@klass ||= Object.const_get(class_name.to_s.camelize)
|
33
|
+
when :has_many, :has_one
|
34
|
+
@klass ||= Object.const_get(class_name.to_s.singularize.camelize)
|
35
|
+
else
|
36
|
+
raise "#{@name} is not a relation. This isn't supposed to happen."
|
37
|
+
end
|
30
38
|
end
|
31
39
|
end
|
40
|
+
|
41
|
+
def class_const_get
|
42
|
+
Kernel::const_get(classify)
|
43
|
+
end
|
44
|
+
|
45
|
+
def through_class
|
46
|
+
Kernel::const_get(@options[:through].to_s.classify)
|
47
|
+
end
|
32
48
|
end
|
33
49
|
end
|
34
50
|
end
|
@@ -16,7 +16,7 @@ module MotionModel
|
|
16
16
|
def should_return(column) #nodoc
|
17
17
|
skippable = [:id]
|
18
18
|
skippable += [:created_at, :updated_at] unless @expose_auto_date_fields
|
19
|
-
!skippable.include?(column) && !
|
19
|
+
!skippable.include?(column) && !relation_column?(column)
|
20
20
|
end
|
21
21
|
|
22
22
|
def returnable_columns #nodoc
|
@@ -26,14 +26,14 @@ module MotionModel
|
|
26
26
|
def default_hash_for(column, value)
|
27
27
|
{:key => column.to_sym,
|
28
28
|
:title => column.to_s.humanize,
|
29
|
-
:type => FORMOTION_MAP[
|
29
|
+
:type => FORMOTION_MAP[column_type(column)],
|
30
30
|
:placeholder => column.to_s.humanize,
|
31
31
|
:value => value
|
32
32
|
}
|
33
33
|
end
|
34
34
|
|
35
35
|
def is_date_time?(column)
|
36
|
-
column_type =
|
36
|
+
column_type = column_type(column)
|
37
37
|
[:date, :time].include?(column_type)
|
38
38
|
end
|
39
39
|
|
@@ -78,7 +78,7 @@ module MotionModel
|
|
78
78
|
# you say so, offering you the opportunity to validate your form data.
|
79
79
|
def from_formotion!(data)
|
80
80
|
self.returnable_columns.each{|column|
|
81
|
-
if data[column] &&
|
81
|
+
if data[column] && column_type(column) == :date || column_type(column) == :time
|
82
82
|
data[column] = Time.at(data[column]) unless data[column].nil?
|
83
83
|
end
|
84
84
|
value = self.send("#{column}=", data[column])
|