motion_model 0.4.6 → 0.5.0
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/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
|