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 ADDED
@@ -0,0 +1,14 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ bubble-wrap (1.4.0)
5
+ motion-stump (0.3.0)
6
+ rake (10.1.0)
7
+
8
+ PLATFORMS
9
+ ruby
10
+
11
+ DEPENDENCIES
12
+ bubble-wrap
13
+ motion-stump (~> 0.2)
14
+ rake
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 => :presence => true
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
- MotionModel now has support for the cool [Formotion gem](https://github.com/clayallsopp/formotion).
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 this example).
667
+ I've shown in the first example).
645
668
 
646
- This feature is extremely experimental, but here's how it works:
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 pass in:
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
 
@@ -70,7 +70,7 @@ module MotionModel
70
70
 
71
71
  # performs a set-inclusion test.
72
72
  #
73
- # Task.find(:id).id([3, 5, 9])
73
+ # Task.find(:id).in([3, 5, 9])
74
74
  def in(set)
75
75
  @collection = @collection.collect do |item|
76
76
  item if set.include?(item.send(@field_name.to_sym))
@@ -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)
@@ -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
- # ateParser.parse_date "There is a date in here tomorrow at 9:00 AM EDT"
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.
@@ -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.
@@ -32,7 +32,8 @@ module MotionModel
32
32
  end
33
33
 
34
34
  def cast_to_array(arg)
35
- Array(arg)
35
+ array=*arg
36
+ array
36
37
  end
37
38
 
38
39
  def cast_to_hash(arg)
data/motion/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  # or forward port their code to take advantage
5
5
  # of adapters.
6
6
  module MotionModel
7
- VERSION = "0.4.6"
7
+ VERSION = "0.5.0"
8
8
  end
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
@@ -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
@@ -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.6
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-06-17 00:00:00.000000000 Z
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