motion_model 0.4.6 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +14 -0
- data/README.md +79 -8
- data/motion/adapters/array_finder_query.rb +1 -1
- data/motion/adapters/array_model_persistence.rb +1 -1
- data/motion/date_parser.rb +7 -1
- data/motion/model/formotion.rb +70 -0
- data/motion/model/model_casts.rb +2 -1
- data/motion/version.rb +1 -1
- data/motion_model.gemspec +1 -1
- data/spec/array_model_persistence_spec.rb +14 -0
- data/spec/date_spec.rb +22 -0
- data/spec/formotion_spec.rb +84 -0
- data/spec/model_casting_spec.rb +6 -1
- metadata +5 -4
data/Gemfile.lock
ADDED
data/README.md
CHANGED
@@ -99,8 +99,8 @@ Getting Going
|
|
99
99
|
|
100
100
|
If you are using Bundler, put this in your Gemfile:
|
101
101
|
|
102
|
-
```
|
103
|
-
gem motion_model
|
102
|
+
```ruby
|
103
|
+
gem 'motion_model'
|
104
104
|
```
|
105
105
|
|
106
106
|
then do:
|
@@ -178,7 +178,7 @@ class Task
|
|
178
178
|
columns :name => :string,
|
179
179
|
:long_name => :string,
|
180
180
|
:due_date => :date
|
181
|
-
validates :name
|
181
|
+
validates :name, :presence => true
|
182
182
|
end
|
183
183
|
|
184
184
|
class MyCoolController
|
@@ -635,15 +635,38 @@ Core Extensions
|
|
635
635
|
Again, a reversing rule is required for both singularize and
|
636
636
|
pluralize to work properly.
|
637
637
|
|
638
|
+
Serialization
|
639
|
+
----------------------
|
640
|
+
|
641
|
+
The `ArrayModelAdapter` does not, by default perform any serialization. That's
|
642
|
+
because how often which parts of your object graph are serialized can affect
|
643
|
+
application performance. However, you *will* want to use the serialization
|
644
|
+
features. Here they are:
|
645
|
+
|
646
|
+
YourModel.deserialize_from_file(file_name = nil)
|
647
|
+
|
648
|
+
YourModel.serialize_to_file(file_name = nil)
|
649
|
+
|
650
|
+
What happens here? When you want to save a model, you call `serialize_to_file`.
|
651
|
+
Each model's data must be saved to a different file so name them accordingly.
|
652
|
+
If you have a model that contains related model objects, you may want to save
|
653
|
+
both models. But you have complete say over that and *the responsibility to
|
654
|
+
handle it*.
|
655
|
+
|
656
|
+
When you call `deserialize_from_file`, your model is populated from the file
|
657
|
+
previously serialized.
|
658
|
+
|
638
659
|
Formotion Support
|
639
660
|
----------------------
|
640
661
|
|
641
|
-
|
662
|
+
### Background
|
663
|
+
|
664
|
+
MotionModel has support for the cool [Formotion gem](https://github.com/clayallsopp/formotion).
|
642
665
|
Note that the Formotion project on GitHub appears to be way ahead of the gem on Rubygems, so you
|
643
666
|
might want to build it yourself if you want the latest gee-whiz features (like `:picker_type`, as
|
644
|
-
I've shown in
|
667
|
+
I've shown in the first example).
|
645
668
|
|
646
|
-
|
669
|
+
### High-Level View
|
647
670
|
|
648
671
|
```ruby
|
649
672
|
class Event
|
@@ -660,9 +683,57 @@ This declares the class. The only difference is that you include `MotionModel::F
|
|
660
683
|
If you want to pass additional information on to Formotion, simply include it in the
|
661
684
|
`:formotion` hash as shown above.
|
662
685
|
|
686
|
+
> Note: the `:formation` stuff in the `columns` specification is something I'm still thinking about. Read on to find out about the two alternate syntaxes for `to_formotion`.
|
687
|
+
|
688
|
+
### Details About `to_formotion`
|
689
|
+
|
690
|
+
There are two alternate syntaxes for calling this. The initial, or "legacy" syntax is as follows:
|
691
|
+
|
692
|
+
```
|
693
|
+
to_formotion(form_title, expose_auto_date_fields, first_section_title)
|
694
|
+
```
|
695
|
+
|
696
|
+
In the legacy syntax, all arguments are optional and sensible defaults are chosen. However, when you want to tune how your form is presented, the syntax gets a bit burdensome. The alternate syntax is:
|
697
|
+
|
698
|
+
```
|
699
|
+
to_formotion(options)
|
700
|
+
```
|
701
|
+
|
702
|
+
The options hash looks a lot like a Formotion hash might, except without the data. Here is an example:
|
703
|
+
|
704
|
+
```
|
705
|
+
{title: 'A very fine form',
|
706
|
+
sections: [
|
707
|
+
{title: 'First Section',
|
708
|
+
fields: [:name, :gender]
|
709
|
+
},
|
710
|
+
{title: 'Second Section',
|
711
|
+
fields: [:address, :city, :state]
|
712
|
+
}
|
713
|
+
]}
|
714
|
+
```
|
715
|
+
|
716
|
+
Note that in this syntax, you can specify a button in the fields array:
|
717
|
+
|
718
|
+
```
|
719
|
+
{title: 'A very fine form',
|
720
|
+
sections: [
|
721
|
+
{title: 'First Section',
|
722
|
+
fields: [:name, :gender]
|
723
|
+
},
|
724
|
+
{title: 'Second Section',
|
725
|
+
fields: [:address, :city, :state, {type: :submit, title: 'Ok'}]
|
726
|
+
}
|
727
|
+
]}
|
728
|
+
```
|
729
|
+
|
730
|
+
This specifies exactly what titles and fields appear where and in what order.
|
731
|
+
|
732
|
+
### How Values Are Produced for Formotion
|
733
|
+
|
663
734
|
MotionModel has sensible defaults for each type supported, so any field of `:date`
|
664
735
|
type will default to a date picker in the Formotion form. However, if you want it
|
665
|
-
to be a string for some reason, just
|
736
|
+
to be a string for some reason, just specify this in `columns`:
|
666
737
|
|
667
738
|
```ruby
|
668
739
|
:date => {:type => :date, :formotion => {:type => :string}}
|
@@ -671,7 +742,7 @@ to be a string for some reason, just pass in:
|
|
671
742
|
To initialize a form from a model in your controller:
|
672
743
|
|
673
744
|
```ruby
|
674
|
-
@form = Formotion::Form.new(@event.to_formotion('event details'))
|
745
|
+
@form = Formotion::Form.new(@event.to_formotion('event details')) # Legacy syntax
|
675
746
|
@form_controller = MyFormController.alloc.initWithForm(@form)
|
676
747
|
```
|
677
748
|
|
@@ -128,7 +128,7 @@ module MotionModel
|
|
128
128
|
def encodeWithCoder(coder)
|
129
129
|
columns.each do |attr|
|
130
130
|
# Serialize attributes except the proxy has_many and belongs_to ones.
|
131
|
-
unless [:belongs_to, :has_many].include? column(attr).type
|
131
|
+
unless [:belongs_to, :has_many, :has_one].include? column(attr).type
|
132
132
|
value = self.send(attr)
|
133
133
|
unless value.nil?
|
134
134
|
coder.encodeObject(value, forKey: attr.to_s)
|
data/motion/date_parser.rb
CHANGED
@@ -5,12 +5,18 @@ module DateParser
|
|
5
5
|
#
|
6
6
|
# => 2013-02-20 09:00:00 -0800
|
7
7
|
def self.parse_date(date_string)
|
8
|
+
if date_string.match(/\d{2}T\d{2}/)
|
9
|
+
# Discards fractional seconds.
|
10
|
+
date_string = date_string.split('.').first if date_string =~ /\.\d{3}Z$/
|
11
|
+
return Time.iso8601(date_string)
|
12
|
+
end
|
13
|
+
|
8
14
|
detect(date_string).first.date
|
9
15
|
end
|
10
16
|
|
11
17
|
# Parse time zone from date
|
12
18
|
#
|
13
|
-
#
|
19
|
+
# DateParser.parse_date "There is a date in here tomorrow at 9:00 AM EDT"
|
14
20
|
#
|
15
21
|
# Caveat: This is implemented per Apple documentation. I've never really
|
16
22
|
# seen it work.
|
data/motion/model/formotion.rb
CHANGED
@@ -66,6 +66,8 @@ module MotionModel
|
|
66
66
|
# If you want a title for your Formotion form, set the <tt>form_title</tt>
|
67
67
|
# argument to a string that will become that title.
|
68
68
|
def to_formotion(form_title = nil, expose_auto_date_fields = false, first_section_title = nil)
|
69
|
+
return new_to_formotion(form_title) if form_title.is_a? Hash
|
70
|
+
|
69
71
|
@expose_auto_date_fields = expose_auto_date_fields
|
70
72
|
|
71
73
|
sections = {
|
@@ -101,6 +103,74 @@ module MotionModel
|
|
101
103
|
form
|
102
104
|
end
|
103
105
|
|
106
|
+
# <tt>new_to_formotion</tt> maps a MotionModel into a hash in a user-definable
|
107
|
+
# manner, according to options.
|
108
|
+
#
|
109
|
+
# form_title: String for form title
|
110
|
+
# sections: Array of sections
|
111
|
+
#
|
112
|
+
# Within sections, use these keys:
|
113
|
+
#
|
114
|
+
# title: String for section title
|
115
|
+
# field: Name of field in your model (Symbol)
|
116
|
+
#
|
117
|
+
# Hash looks something like this:
|
118
|
+
#
|
119
|
+
# {sections: [
|
120
|
+
# {title: 'First Section', # First section
|
121
|
+
# fields: [:name, :gender] # contains name and gender
|
122
|
+
# },
|
123
|
+
# {title: 'Second Section',
|
124
|
+
# fields: [:address, :city, :state], # Second section, address
|
125
|
+
# {title: 'Submit', type: :submit} # city, state add submit button
|
126
|
+
# }
|
127
|
+
# ]}
|
128
|
+
def new_to_formotion(options = {form_title: nil, sections: []})
|
129
|
+
form = {}
|
130
|
+
|
131
|
+
@expose_auto_date_fields = options[:auto_date_fields]
|
132
|
+
|
133
|
+
fields = returnable_columns
|
134
|
+
form[:title] = options[:form_title] unless options[:form_title].nil?
|
135
|
+
fill_from_options(form, options) if options[:sections]
|
136
|
+
form
|
137
|
+
end
|
138
|
+
|
139
|
+
def fill_from_options(form, options)
|
140
|
+
form[:sections] ||= []
|
141
|
+
|
142
|
+
options[:sections].each do |section|
|
143
|
+
form[:sections] << fill_section(section)
|
144
|
+
end
|
145
|
+
form
|
146
|
+
end
|
147
|
+
|
148
|
+
def fill_section(section)
|
149
|
+
new_section = {}
|
150
|
+
|
151
|
+
section.each_pair do |key, value|
|
152
|
+
case key
|
153
|
+
when :title
|
154
|
+
new_section[:title] = value unless value.nil?
|
155
|
+
when :fields
|
156
|
+
new_section[:rows] ||= []
|
157
|
+
value.each do |field_or_hash|
|
158
|
+
new_section[:rows].push(fill_row(field_or_hash))
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
new_section
|
163
|
+
end
|
164
|
+
|
165
|
+
def fill_row(field_or_hash)
|
166
|
+
case field_or_hash
|
167
|
+
when Hash
|
168
|
+
return field_or_hash unless field_or_hash.keys.detect{|key| key =~ /^formotion_/}
|
169
|
+
else
|
170
|
+
combine_options field_or_hash, default_hash_for(field_or_hash, self.send(field_or_hash))
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
104
174
|
# <tt>from_formotion</tt> takes the information rendered from a Formotion
|
105
175
|
# form and stuffs it back into a MotionModel. This data is not saved until
|
106
176
|
# you say so, offering you the opportunity to validate your form data.
|
data/motion/model/model_casts.rb
CHANGED
data/motion/version.rb
CHANGED
data/motion_model.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |gem|
|
|
12
12
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
13
13
|
gem.name = "motion_model"
|
14
14
|
gem.require_paths = ["lib"]
|
15
|
-
gem.add_dependency 'bubble-wrap', '1.3.0'
|
15
|
+
gem.add_dependency 'bubble-wrap', '>= 1.3.0'
|
16
16
|
gem.add_dependency 'motion-support', '>=0.1.0'
|
17
17
|
gem.version = MotionModel::VERSION
|
18
18
|
end
|
@@ -193,6 +193,7 @@ class Parent
|
|
193
193
|
include MotionModel::ArrayModelAdapter
|
194
194
|
columns :name
|
195
195
|
has_many :children
|
196
|
+
has_one :dog
|
196
197
|
end
|
197
198
|
|
198
199
|
class Child
|
@@ -202,28 +203,41 @@ class Child
|
|
202
203
|
belongs_to :parent
|
203
204
|
end
|
204
205
|
|
206
|
+
class Dog
|
207
|
+
include MotionModel::Model
|
208
|
+
include MotionModel::ArrayModelAdapter
|
209
|
+
columns :name
|
210
|
+
belongs_to :parent
|
211
|
+
end
|
212
|
+
|
205
213
|
describe "serialization of relations" do
|
206
214
|
before do
|
207
215
|
parent = Parent.create(:name => 'BoB')
|
208
216
|
parent.children.create :name => 'Fergie'
|
209
217
|
parent.children.create :name => 'Will I Am'
|
218
|
+
parent.dog.create :name => 'Fluffy'
|
210
219
|
end
|
211
220
|
|
212
221
|
it "is wired up right" do
|
213
222
|
Parent.first.name.should == 'BoB'
|
214
223
|
Parent.first.children.count.should == 2
|
224
|
+
Parent.first.dog.count.should == 1
|
215
225
|
end
|
216
226
|
|
217
227
|
it "serializes and deserializes properly" do
|
218
228
|
Parent.serialize_to_file('parents.dat')
|
219
229
|
Child.serialize_to_file('children.dat')
|
230
|
+
Dog.serialize_to_file('dogs.dat')
|
220
231
|
Parent.delete_all
|
221
232
|
Child.delete_all
|
233
|
+
Dog.delete_all
|
222
234
|
Parent.deserialize_from_file('parents.dat')
|
223
235
|
Child.deserialize_from_file('children.dat')
|
236
|
+
Dog.deserialize_from_file('dogs.dat')
|
224
237
|
Parent.first.name.should == 'BoB'
|
225
238
|
Parent.first.children.count.should == 2
|
226
239
|
Parent.first.children.first.name.should == 'Fergie'
|
240
|
+
Parent.first.dog.first.name.should == 'Fluffy'
|
227
241
|
end
|
228
242
|
end
|
229
243
|
|
data/spec/date_spec.rb
CHANGED
@@ -52,5 +52,27 @@ describe "time conversions" do
|
|
52
52
|
|
53
53
|
end
|
54
54
|
|
55
|
+
describe "parsing ISO8601 date formats" do
|
56
|
+
class Model
|
57
|
+
include MotionModel::Model
|
58
|
+
include MotionModel::ArrayModelAdapter
|
59
|
+
columns :test_date => :date,
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'parses ISO8601 format variant #1 (RoR default)' do
|
63
|
+
m = Model.new(test_date: '2012-04-23T18:25:43Z')
|
64
|
+
m.test_date.should.not.be.nil
|
65
|
+
end
|
55
66
|
|
67
|
+
it 'parses ISO8601 variant #2, 3DP Accuracy (RoR4), JavaScript built-in JSON object' do
|
68
|
+
m = Model.new(test_date: '2012-04-23T18:25:43.511Z')
|
69
|
+
m.test_date.should.not.be.nil
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'parses ISO8601 variant #3' do
|
73
|
+
m = Model.new(test_date: '2012-04-23 18:25:43 +0000')
|
74
|
+
m.test_date.should.not.be.nil
|
75
|
+
m.test_date.utc.to_s.should.eql '2012-04-23 18:25:43 UTC'
|
76
|
+
end
|
77
|
+
end
|
56
78
|
end
|
data/spec/formotion_spec.rb
CHANGED
@@ -97,4 +97,88 @@ describe "formotion" do
|
|
97
97
|
it "does not include related columns in the collection" do
|
98
98
|
result = @subject.to_formotion[:sections].first[:rows].has_hash_value?(:related_models).should == false
|
99
99
|
end
|
100
|
+
|
101
|
+
describe "new syntax" do
|
102
|
+
it "generates a formotion hash" do
|
103
|
+
@subject.new_to_formotion.should.not.be.nil
|
104
|
+
end
|
105
|
+
|
106
|
+
it "has the correct form title" do
|
107
|
+
@subject.new_to_formotion(form_title: 'test form')[:title].should == 'test form'
|
108
|
+
end
|
109
|
+
|
110
|
+
it "has two sections" do
|
111
|
+
s = @subject.new_to_formotion(
|
112
|
+
sections: [
|
113
|
+
{title: 'one'},
|
114
|
+
{title: 'two'}
|
115
|
+
]
|
116
|
+
)[:sections].length.should == 2
|
117
|
+
end
|
118
|
+
|
119
|
+
it "does not include title in the default section" do
|
120
|
+
@subject.new_to_formotion(
|
121
|
+
sections: [
|
122
|
+
{fields: [:name]},
|
123
|
+
{title: 'two'}
|
124
|
+
]
|
125
|
+
)[:sections].first[:title].should == nil
|
126
|
+
end
|
127
|
+
|
128
|
+
it "does include address in the second section" do
|
129
|
+
@subject.new_to_formotion(
|
130
|
+
sections: [
|
131
|
+
{fields: [:name]},
|
132
|
+
{title: 'two'}
|
133
|
+
]
|
134
|
+
)[:sections][1][:title].should.not == nil
|
135
|
+
end
|
136
|
+
|
137
|
+
it "has two rows in the first section" do
|
138
|
+
@subject.new_to_formotion(
|
139
|
+
sections: [
|
140
|
+
{fields: [:name, :date]},
|
141
|
+
{title: 'two'}
|
142
|
+
]
|
143
|
+
)[:sections][0][:rows].length.should == 2
|
144
|
+
end
|
145
|
+
|
146
|
+
it "has two rows in the first section" do
|
147
|
+
@subject.new_to_formotion(
|
148
|
+
sections: [
|
149
|
+
{fields: [:name, :date]},
|
150
|
+
{title: 'two'}
|
151
|
+
]
|
152
|
+
)[:sections][0][:rows].length.should == 2
|
153
|
+
end
|
154
|
+
|
155
|
+
it "value of location row in :address section is 'my house'" do
|
156
|
+
@subject.new_to_formotion(
|
157
|
+
sections: [
|
158
|
+
{title: 'name', fields: [:name, :date]},
|
159
|
+
{title: 'address', fields: [:location]}
|
160
|
+
]
|
161
|
+
)[:sections][1][:rows].first[:value].should == 'my house'
|
162
|
+
end
|
163
|
+
it "value of name row is 'get together'" do
|
164
|
+
@subject.new_to_formotion(
|
165
|
+
sections: [
|
166
|
+
{title: 'name', fields: [:name, :date]},
|
167
|
+
{title: 'address', fields: [:location]}
|
168
|
+
]
|
169
|
+
)[:sections][1][:rows].first[:value].should == 'my house'
|
170
|
+
end
|
171
|
+
it "allows you to place buttons in your form" do
|
172
|
+
result = @subject.new_to_formotion(
|
173
|
+
sections: [
|
174
|
+
{title: 'name', fields: [:name, :date, {title: 'Submit', type: :submit}]},
|
175
|
+
{title: 'address', fields: [:location]}
|
176
|
+
]
|
177
|
+
)
|
178
|
+
|
179
|
+
result[:sections][0][:rows][2].should.is_a? Hash
|
180
|
+
result[:sections][0][:rows][2].should.has_key?(:type)
|
181
|
+
result[:sections][0][:rows][2][:type].should == :submit
|
182
|
+
end
|
183
|
+
end
|
100
184
|
end
|
data/spec/model_casting_spec.rb
CHANGED
@@ -116,7 +116,12 @@ describe 'Type casting' do
|
|
116
116
|
it 'returns an Array for an array field' do
|
117
117
|
@convertible.an_array.should.is_a(Array)
|
118
118
|
end
|
119
|
-
|
119
|
+
it 'returns proper array for parsed json data using bubble wrap' do
|
120
|
+
parsed_json = BW::JSON.parse('{"menu_categories":["Lunch"]}')
|
121
|
+
@convertible.an_array = parsed_json["menu_categories"]
|
122
|
+
@convertible.an_array.count.should == 1
|
123
|
+
@convertible.an_array.include?("Lunch").should == true
|
124
|
+
end
|
120
125
|
it 'the array field should be the same as the range form' do
|
121
126
|
(@convertible.an_array.first..@convertible.an_array.last).should.equal(1..10)
|
122
127
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,14 +9,14 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bubble-wrap
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- - '
|
19
|
+
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
21
|
version: 1.3.0
|
22
22
|
type: :runtime
|
@@ -24,7 +24,7 @@ dependencies:
|
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- - '
|
27
|
+
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: 1.3.0
|
30
30
|
- !ruby/object:Gem::Dependency
|
@@ -54,6 +54,7 @@ files:
|
|
54
54
|
- .travis.yml
|
55
55
|
- CHANGELOG
|
56
56
|
- Gemfile
|
57
|
+
- Gemfile.lock
|
57
58
|
- LICENSE
|
58
59
|
- README.md
|
59
60
|
- Rakefile
|