motion_model 0.4.4 → 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/Gemfile +1 -0
- data/README.md +58 -1
- data/Rakefile +4 -2
- data/lib/motion_model.rb +1 -1
- data/lib/motion_model/date_parser.rb +66 -0
- data/{lib/motion_model/model → motion/adapters}/array_finder_query.rb +9 -9
- data/{lib/motion_model → motion}/adapters/array_model_adapter.rb +0 -0
- data/{lib/motion_model → motion}/adapters/array_model_persistence.rb +0 -0
- data/motion/date_parser.rb +66 -0
- data/{lib/motion_model → motion}/ext.rb +0 -0
- data/{lib/motion_model → motion}/input_helpers.rb +0 -0
- data/{lib/motion_model → motion}/model/column.rb +0 -0
- data/{lib/motion_model → motion}/model/formotion.rb +37 -9
- data/{lib/motion_model → motion}/model/model.rb +26 -10
- data/{lib/motion_model → motion}/model/model_casts.rb +5 -3
- data/{lib/motion_model → motion}/model/transaction.rb +0 -0
- data/{lib/motion_model → motion}/validatable.rb +0 -0
- data/{lib/motion_model → motion}/version.rb +1 -1
- data/motion_model.gemspec +2 -2
- data/spec/finder_spec.rb +41 -2
- data/spec/formotion_spec.rb +29 -6
- data/spec/kvo_config_clone_spec.rb +35 -0
- metadata +20 -16
data/CHANGELOG
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -20,6 +20,7 @@ MotionModel is MIT licensed, which means you can pretty much do whatever
|
|
20
20
|
you like with it. See the LICENSE file in this project.
|
21
21
|
|
22
22
|
* [Getting Going](#getting-going)
|
23
|
+
* [Bugs, Features, and Issues, Oh My!](#bugs-features-and-issues-oh-my)
|
23
24
|
* [What Model Can Do](#what-model-can-do)
|
24
25
|
* [Model Data Types](#model-data-types)
|
25
26
|
* [Validation Methods](#validation-methods)
|
@@ -32,11 +33,40 @@ you like with it. See the LICENSE file in this project.
|
|
32
33
|
* [Problems/Comments](#problemscomments)
|
33
34
|
* [Submissions/Patches](#submissionspatches)
|
34
35
|
|
36
|
+
## Bugs, Features, and Issues, Oh My!
|
37
|
+
|
38
|
+
The reason this is up front here is that in order to respond to your issues
|
39
|
+
we need you to help us out by reading these guidelines. You can also look at
|
40
|
+
[Submissions/Patches](#submissionspatches) near the bottom of this README
|
41
|
+
which restates a bit of this.
|
42
|
+
|
43
|
+
That said, all software has bugs, and anyone who thinks otherwise probably is smarter than I am. There are going to be edge cases or cases that our tests don't cover. And that's why open source is great: other people will run into issues we can fix. Other people will have needs we don't have but that are of general utility. And so on.
|
44
|
+
|
45
|
+
But… fair is fair. We would make the following requests of you:
|
46
|
+
|
47
|
+
* Debug the code as far as you can. Obviously, there are times when you just won't be able to see what's wrong or where there's some squirrely interaction with RubyMotion.
|
48
|
+
* If you are comfortable with the MotionModel code, please try to write a spec that makes it fail and submit a pull request with that failing spec. The isolated test case helps us narrow down what changed and to know when we have the issue fixed. Two things make this even better:
|
49
|
+
1. Our specs become more comprehensive; and
|
50
|
+
2. If the issue is an interaction between MotionModel and RubyMotion, it's easier to pass along to HipByte and have a spec they can use for a quick hitting test case. Even better, fix the bug and submit that fix *and* the spec in a pull request.
|
51
|
+
* If you are not comfortable with the MotionModel code, then go ahead and describe the issue in as much detail as possible, including backtraces from the debugger, if appropriate.
|
52
|
+
|
53
|
+
Now, I've belabored the point about bug reporting enough. The point is, if you possibly can, write a spec.
|
54
|
+
|
55
|
+
Issues: Please mark your issues as questions or feature requests, depending on which they are. We'll do all we can to review them and answer questions as quickly as possible. For feature requests, you really can implement the feature in many cases and then submit a pull request. If not, we'll leave it open for consideration in future releases.
|
56
|
+
|
57
|
+
### Summary
|
58
|
+
|
59
|
+
Bugs: Please write a failing spec
|
60
|
+
|
61
|
+
Issues: Please mark them as question or request
|
62
|
+
|
35
63
|
Changes for Existing Users to Be Aware Of
|
36
64
|
=================
|
37
65
|
|
38
66
|
Please see the CHANGELOG for update on changes.
|
39
67
|
|
68
|
+
Version 0.4.4 is the first version to be gem-compatible with RubyMotion 2.0
|
69
|
+
|
40
70
|
Version 0.3.8 to 0.4.0 is a minor version bump, not a patch version. Upgrading
|
41
71
|
to 0.4.0 *will break existing code*. To update your code, simply insert the following line:
|
42
72
|
|
@@ -645,7 +675,7 @@ To initialize a form from a model in your controller:
|
|
645
675
|
@form_controller = MyFormController.alloc.initWithForm(@form)
|
646
676
|
```
|
647
677
|
|
648
|
-
The magic is in: `MotionModel::Model#to_formotion(
|
678
|
+
The magic is in: `MotionModel::Model#to_formotion(form_title)`.
|
649
679
|
|
650
680
|
The auto_date fields `created_at` and `updated_at` are not sent to
|
651
681
|
Formotion by default. If you want them sent to Formotion, set the
|
@@ -664,6 +694,33 @@ On the flip side you do something like this in your Formotion submit handler:
|
|
664
694
|
This performs sets on each field. You'll, of course, want to check your
|
665
695
|
validations before dismissing the form.
|
666
696
|
|
697
|
+
Moreover, Formotion support allows you to split one model fields in sections.
|
698
|
+
By default all fields are put in a single untitled section. Here is a complete
|
699
|
+
example:
|
700
|
+
|
701
|
+
```ruby
|
702
|
+
class Event
|
703
|
+
include MotionModel::Model
|
704
|
+
include MotionModel::Formotion # <== Formotion support
|
705
|
+
|
706
|
+
columns :name => :string,
|
707
|
+
:date => {:type => :date, :formotion => {:picker_type => :date_time}},
|
708
|
+
:location => {:type => :string, :formotion => {:section => :address}}
|
709
|
+
|
710
|
+
has_formotion_sections :address => {:title => "Address"}
|
711
|
+
end
|
712
|
+
```
|
713
|
+
|
714
|
+
This will create a form with the `name` and `date` fields presented first, then a
|
715
|
+
section titled 'Address' will contain the `location` field.
|
716
|
+
|
717
|
+
If you want to add a title to the first section, provide a :first_section_title
|
718
|
+
argument to `to_formotion`:
|
719
|
+
|
720
|
+
```ruby
|
721
|
+
@form = Formotion::Form.new(@event.to_formotion('event details', true, 'First Section Title'))
|
722
|
+
```
|
723
|
+
|
667
724
|
Problems/Comments
|
668
725
|
------------------
|
669
726
|
|
data/Rakefile
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
$:.unshift("/Library/RubyMotion/lib")
|
4
|
-
require 'motion/project'
|
4
|
+
require 'motion/project/template/ios'
|
5
5
|
require 'bundler'
|
6
6
|
Bundler.require
|
7
7
|
|
8
|
+
$:.unshift(File.expand_path('../lib', __FILE__))
|
9
|
+
require 'motion_model'
|
10
|
+
|
8
11
|
Motion::Project::App.setup do |app|
|
9
12
|
# Use `rake config' to see complete project settings.
|
10
13
|
app.name = 'MotionModel'
|
11
14
|
app.delegate_class = 'FakeDelegate'
|
12
|
-
app.files = Dir.glob('./lib/motion_model/**/*.rb') + app.files
|
13
15
|
app.files = (app.files + Dir.glob('./app/**/*.rb')).uniq
|
14
16
|
end
|
data/lib/motion_model.rb
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
module DateParser
|
2
|
+
# Parse a date string: E.g.:
|
3
|
+
#
|
4
|
+
# DateParser.parse_date "There is a date in here tomorrow at 9:00 AM"
|
5
|
+
#
|
6
|
+
# => 2013-02-20 09:00:00 -0800
|
7
|
+
def self.parse_date(date_string)
|
8
|
+
detect(date_string).first.date
|
9
|
+
end
|
10
|
+
|
11
|
+
# Parse time zone from date
|
12
|
+
#
|
13
|
+
# ateParser.parse_date "There is a date in here tomorrow at 9:00 AM EDT"
|
14
|
+
#
|
15
|
+
# Caveat: This is implemented per Apple documentation. I've never really
|
16
|
+
# seen it work.
|
17
|
+
def self.parse_time_zone(date_string)
|
18
|
+
detect(date_string).first.timeZone
|
19
|
+
end
|
20
|
+
|
21
|
+
# Parse a date string: E.g.:
|
22
|
+
#
|
23
|
+
# SugarCube::DateParser.parse_date "You have a meeting from 9:00 AM to 3:00 PM"
|
24
|
+
#
|
25
|
+
# => 21600.0
|
26
|
+
#
|
27
|
+
# Divide by 3600.0 to get number of hours duration.
|
28
|
+
def self.parse_duration(date_string)
|
29
|
+
detect(date_string).first.send(:duration)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Parse a date into a raw match array for further processing
|
33
|
+
def self.match(date_string)
|
34
|
+
detect(date_string)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def self.detect(date_string)
|
39
|
+
error = Pointer.new(:object)
|
40
|
+
detector = NSDataDetector.dataDetectorWithTypes(NSTextCheckingTypeDate, error:error)
|
41
|
+
matches = detector.matchesInString(date_string, options:0, range:NSMakeRange(0, date_string.length))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
class String
|
47
|
+
# Use NSDataDetector to parse a string containing a date
|
48
|
+
# or duration. These can be of the form:
|
49
|
+
#
|
50
|
+
# "tomorrow at 7:30 PM"
|
51
|
+
# "11.23.2013"
|
52
|
+
# "from 7:30 to 10:00 AM"
|
53
|
+
#
|
54
|
+
# etc.
|
55
|
+
def to_date
|
56
|
+
DateParser.parse_date(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_timezone
|
60
|
+
DateParser.parse_time_zone(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_duration
|
64
|
+
DateParser.parse_duration(self)
|
65
|
+
end
|
66
|
+
end
|
@@ -148,15 +148,15 @@ module MotionModel
|
|
148
148
|
alias_method :not_equal, :ne
|
149
149
|
|
150
150
|
########### accessor methods #########
|
151
|
-
|
152
|
-
# returns first element that matches.
|
153
|
-
def first
|
154
|
-
|
151
|
+
|
152
|
+
# returns first element or count elements that matches.
|
153
|
+
def first(*args)
|
154
|
+
to_a.send(:first, *args)
|
155
155
|
end
|
156
|
-
|
157
|
-
# returns last element that matches.
|
158
|
-
def last
|
159
|
-
|
156
|
+
|
157
|
+
# returns last element or count elements that matches.
|
158
|
+
def last(*args)
|
159
|
+
to_a.send(:last, *args)
|
160
160
|
end
|
161
161
|
|
162
162
|
# returns all elements that match as an array.
|
@@ -166,7 +166,7 @@ module MotionModel
|
|
166
166
|
|
167
167
|
# returns all elements that match as an array.
|
168
168
|
def to_a
|
169
|
-
@collection
|
169
|
+
@collection || []
|
170
170
|
end
|
171
171
|
|
172
172
|
# each is a shortcut method to turn a query into an iterator. It allows
|
File without changes
|
File without changes
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module DateParser
|
2
|
+
# Parse a date string: E.g.:
|
3
|
+
#
|
4
|
+
# DateParser.parse_date "There is a date in here tomorrow at 9:00 AM"
|
5
|
+
#
|
6
|
+
# => 2013-02-20 09:00:00 -0800
|
7
|
+
def self.parse_date(date_string)
|
8
|
+
detect(date_string).first.date
|
9
|
+
end
|
10
|
+
|
11
|
+
# Parse time zone from date
|
12
|
+
#
|
13
|
+
# ateParser.parse_date "There is a date in here tomorrow at 9:00 AM EDT"
|
14
|
+
#
|
15
|
+
# Caveat: This is implemented per Apple documentation. I've never really
|
16
|
+
# seen it work.
|
17
|
+
def self.parse_time_zone(date_string)
|
18
|
+
detect(date_string).first.timeZone
|
19
|
+
end
|
20
|
+
|
21
|
+
# Parse a date string: E.g.:
|
22
|
+
#
|
23
|
+
# SugarCube::DateParser.parse_date "You have a meeting from 9:00 AM to 3:00 PM"
|
24
|
+
#
|
25
|
+
# => 21600.0
|
26
|
+
#
|
27
|
+
# Divide by 3600.0 to get number of hours duration.
|
28
|
+
def self.parse_duration(date_string)
|
29
|
+
detect(date_string).first.send(:duration)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Parse a date into a raw match array for further processing
|
33
|
+
def self.match(date_string)
|
34
|
+
detect(date_string)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def self.detect(date_string)
|
39
|
+
error = Pointer.new(:object)
|
40
|
+
detector = NSDataDetector.dataDetectorWithTypes(NSTextCheckingTypeDate, error:error)
|
41
|
+
matches = detector.matchesInString(date_string, options:0, range:NSMakeRange(0, date_string.length))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
class String
|
47
|
+
# Use NSDataDetector to parse a string containing a date
|
48
|
+
# or duration. These can be of the form:
|
49
|
+
#
|
50
|
+
# "tomorrow at 7:30 PM"
|
51
|
+
# "11.23.2013"
|
52
|
+
# "from 7:30 to 10:00 AM"
|
53
|
+
#
|
54
|
+
# etc.
|
55
|
+
def to_date
|
56
|
+
DateParser.parse_date(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_timezone
|
60
|
+
DateParser.parse_time_zone(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_duration
|
64
|
+
DateParser.parse_duration(self)
|
65
|
+
end
|
66
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -1,5 +1,15 @@
|
|
1
1
|
module MotionModel
|
2
2
|
module Formotion
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(PublicClassMethods)
|
5
|
+
end
|
6
|
+
module PublicClassMethods
|
7
|
+
def has_formotion_sections(sections = {})
|
8
|
+
define_method( "formotion_sections") do
|
9
|
+
sections
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
3
13
|
FORMOTION_MAP = {
|
4
14
|
:string => :string,
|
5
15
|
:date => :date,
|
@@ -53,22 +63,40 @@ module MotionModel
|
|
53
63
|
# and <tt>updated_at</tt> are suppressed. If you want these shown in
|
54
64
|
# your Formotion form, set <tt>expose_auto_date_fields</tt> to <tt>true</tt>
|
55
65
|
#
|
56
|
-
# If you want a title for your Formotion form, set the <tt>
|
66
|
+
# If you want a title for your Formotion form, set the <tt>form_title</tt>
|
57
67
|
# argument to a string that will become that title.
|
58
|
-
def to_formotion(
|
68
|
+
def to_formotion(form_title = nil, expose_auto_date_fields = false, first_section_title = nil)
|
59
69
|
@expose_auto_date_fields = expose_auto_date_fields
|
60
|
-
form = {
|
61
|
-
sections: [{}]
|
62
|
-
}
|
63
70
|
|
64
|
-
|
65
|
-
|
66
|
-
|
71
|
+
sections = {
|
72
|
+
default: {rows: []}
|
73
|
+
}
|
74
|
+
if respond_to? 'formotion_sections'
|
75
|
+
formotion_sections.each do |k,v|
|
76
|
+
sections[k] = v
|
77
|
+
sections[k][:rows] = []
|
78
|
+
end
|
79
|
+
end
|
80
|
+
sections[:default][:title] ||= first_section_title
|
67
81
|
|
68
82
|
returnable_columns.each do |column|
|
69
83
|
value = value_for(column)
|
70
84
|
h = default_hash_for(column, value)
|
71
|
-
|
85
|
+
s = column(column).options[:formotion] ? column(column).options[:formotion][:section] : nil
|
86
|
+
if s
|
87
|
+
sections[s] ||= {}
|
88
|
+
sections[s][:rows].push(combine_options(column,h))
|
89
|
+
else
|
90
|
+
sections[:default][:rows].push(combine_options(column, h))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
form = {
|
95
|
+
sections: []
|
96
|
+
}
|
97
|
+
form[:title] ||= form_title
|
98
|
+
sections.each do |k,section|
|
99
|
+
form[:sections] << section
|
72
100
|
end
|
73
101
|
form
|
74
102
|
end
|
@@ -226,14 +226,14 @@ module MotionModel
|
|
226
226
|
# Note collection is not emptied, and next_id is not reset.
|
227
227
|
end
|
228
228
|
|
229
|
-
# Retrieves first row of query
|
230
|
-
def first
|
231
|
-
all.first
|
229
|
+
# Retrieves first row or count rows of query
|
230
|
+
def first(*args)
|
231
|
+
all.send(:first, *args)
|
232
232
|
end
|
233
233
|
|
234
|
-
# Retrieves last row of query
|
235
|
-
def last
|
236
|
-
all.last
|
234
|
+
# Retrieves last row or count rows of query
|
235
|
+
def last(*args)
|
236
|
+
all.send(:last, *args)
|
237
237
|
end
|
238
238
|
|
239
239
|
def each(&block)
|
@@ -250,9 +250,21 @@ module MotionModel
|
|
250
250
|
|
251
251
|
private
|
252
252
|
|
253
|
+
attr_accessor :abstract_class
|
254
|
+
|
255
|
+
def config
|
256
|
+
@config ||= begin
|
257
|
+
if !superclass.ancestors.include?(MotionModel::Model) || superclass.abstract_class
|
258
|
+
{}
|
259
|
+
else
|
260
|
+
superclass.send(:config).dup
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
253
265
|
# Hashes to for quick column lookup
|
254
266
|
def _column_hashes
|
255
|
-
|
267
|
+
config[:column_hashes] ||= {}
|
256
268
|
end
|
257
269
|
|
258
270
|
# BUGBUG: This appears not to be executed, therefore @_issue_notifications is always nil to begin with.
|
@@ -630,13 +642,14 @@ module MotionModel
|
|
630
642
|
end
|
631
643
|
|
632
644
|
def set_attr(name, value)
|
633
|
-
|
645
|
+
method = "#{name}=".to_sym
|
646
|
+
respond_to?(method) ? send(method, value) : _set_attr(name, value)
|
634
647
|
end
|
635
648
|
|
636
649
|
def _set_attr(name, value)
|
637
650
|
name = name.to_sym
|
638
651
|
old_value = @data[name]
|
639
|
-
new_value = relation_column?(name) ? value : cast_to_type(name, value)
|
652
|
+
new_value = !column(name) || relation_column?(name) ? value : cast_to_type(name, value)
|
640
653
|
if new_value != old_value
|
641
654
|
@data[name] = new_value
|
642
655
|
@dirty = true
|
@@ -766,7 +779,10 @@ module MotionModel
|
|
766
779
|
self.class.send(:has_relation?, col)
|
767
780
|
end
|
768
781
|
|
769
|
-
def rebuild_relation(
|
782
|
+
def rebuild_relation(col, instance_or_collection, options = {}) # nodoc
|
783
|
+
end
|
784
|
+
|
785
|
+
def unload_relation(col)
|
770
786
|
end
|
771
787
|
|
772
788
|
def initialize_data_columns(column, value) #nodoc
|
@@ -21,9 +21,11 @@ module MotionModel
|
|
21
21
|
def cast_to_date(arg)
|
22
22
|
case arg
|
23
23
|
when String
|
24
|
-
return
|
25
|
-
|
26
|
-
|
24
|
+
return DateParser::parse_date(arg)
|
25
|
+
# return NSDate.dateWithNaturalLanguageString(arg.gsub('-','/'), locale:NSUserDefaults.standardUserDefaults.dictionaryRepresentation)
|
26
|
+
when Time, NSDate
|
27
|
+
return arg
|
28
|
+
# return NSDate.dateWithNaturalLanguageString(arg.strftime('%Y/%m/%d %H:%M:%S'), locale:NSUserDefaults.standardUserDefaults.dictionaryRepresentation)
|
27
29
|
else
|
28
30
|
return arg
|
29
31
|
end
|
File without changes
|
File without changes
|
data/motion_model.gemspec
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
require File.expand_path('../
|
2
|
+
require File.expand_path('../motion/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Steve Ross"]
|
@@ -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
|
data/spec/finder_spec.rb
CHANGED
@@ -55,7 +55,23 @@ describe 'finders' do
|
|
55
55
|
atask = Task.create(:name => 'find me', :details => "details 1")
|
56
56
|
found_task = Task.where(:details).contain("s 1").first.details.should == 'details 1'
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
|
+
it 'should returns first 5 results for where call' do
|
60
|
+
Task.where(:name).contains('task').first(5).length.should == 5
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should returns last 5 results for where call' do
|
64
|
+
Task.where(:name).contains('task').last(5).length.should == 5
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should returns first element for where call' do
|
68
|
+
Task.where(:name).contains('task').first.should.is_a Task
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should returns last element for where call' do
|
72
|
+
Task.where(:name).contains('task').last.should.is_a Task
|
73
|
+
end
|
74
|
+
|
59
75
|
it "performs set inclusion(in) queries" do
|
60
76
|
class InTest
|
61
77
|
include MotionModel::Model
|
@@ -70,7 +86,7 @@ describe 'finders' do
|
|
70
86
|
results = InTest.find(:id).in([3, 5, 7])
|
71
87
|
results.length.should == 3
|
72
88
|
end
|
73
|
-
|
89
|
+
|
74
90
|
it 'handles case-sensitive queries' do
|
75
91
|
task = Task.create :name => 'Bob'
|
76
92
|
Task.find(:name).eq('bob', :case_sensitive => true).all.length.should == 0
|
@@ -88,6 +104,29 @@ describe 'finders' do
|
|
88
104
|
task_id = task.id
|
89
105
|
end
|
90
106
|
end
|
107
|
+
|
108
|
+
it 'should returns first 5 members of the collection as an array' do
|
109
|
+
Task.first(5).length.should.equal(5)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should returns last 5 members of the collection as an array' do
|
113
|
+
Task.last(5).length.should.equal(5)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should be a difference between first and last element' do
|
117
|
+
first_records = Task.first(5)
|
118
|
+
last_records = Task.last(5)
|
119
|
+
|
120
|
+
first_records[0].should.not == last_records[0]
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should returns first element' do
|
124
|
+
Task.first.should.is_a Task
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should returns last element' do
|
128
|
+
Task.last.should.is_a Task
|
129
|
+
end
|
91
130
|
|
92
131
|
describe 'block-style finders' do
|
93
132
|
before do
|
data/spec/formotion_spec.rb
CHANGED
@@ -6,11 +6,14 @@ class ModelWithOptions
|
|
6
6
|
|
7
7
|
columns :name => :string,
|
8
8
|
:date => {:type => :date, :formotion => {:picker_type => :date_time}},
|
9
|
-
:location => :string,
|
9
|
+
:location => {:type => :string, :formotion => {:section => :address}},
|
10
10
|
:created_at => :date,
|
11
11
|
:updated_at => :date
|
12
12
|
|
13
13
|
has_many :related_models
|
14
|
+
|
15
|
+
has_formotion_sections :address => { title: "Address" }
|
16
|
+
|
14
17
|
end
|
15
18
|
|
16
19
|
class RelatedModel
|
@@ -42,12 +45,32 @@ describe "formotion" do
|
|
42
45
|
@subject.to_formotion.should.not.be.nil
|
43
46
|
end
|
44
47
|
|
45
|
-
it "has the correct
|
46
|
-
@subject.to_formotion('test
|
48
|
+
it "has the correct form title" do
|
49
|
+
@subject.to_formotion('test form')[:title].should == 'test form'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "has two sections" do
|
53
|
+
@subject.to_formotion[:sections].length.should == 2
|
54
|
+
end
|
55
|
+
|
56
|
+
it "has 2 rows in default section" do
|
57
|
+
@subject.to_formotion[:sections].first[:rows].length.should == 2
|
58
|
+
end
|
59
|
+
|
60
|
+
it "does not include title in the default section" do
|
61
|
+
@subject.to_formotion[:sections].first[:title].should == nil
|
62
|
+
end
|
63
|
+
|
64
|
+
it "does include title in the :address section" do
|
65
|
+
@subject.to_formotion[:sections][1][:title].should == 'Address'
|
66
|
+
end
|
67
|
+
|
68
|
+
it "has 1 row in :address section" do
|
69
|
+
@subject.to_formotion[:sections][1][:rows].length.should == 1
|
47
70
|
end
|
48
71
|
|
49
|
-
it "
|
50
|
-
@subject.to_formotion
|
72
|
+
it "value of location row in :address section is 'my house'" do
|
73
|
+
@subject.to_formotion[:sections][1][:rows].first[:value].should == 'my house'
|
51
74
|
end
|
52
75
|
|
53
76
|
it "value of name row is 'get together'" do
|
@@ -57,7 +80,7 @@ describe "formotion" do
|
|
57
80
|
it "binds data from rendered form into model fields" do
|
58
81
|
@subject.from_formotion!({:name => '007 Reunion', :date => 1358197323, :location => "Q's Lab"})
|
59
82
|
@subject.name.should == '007 Reunion'
|
60
|
-
@subject.date.strftime("%Y-%m-%d %H:%M").should == '2013-01-14
|
83
|
+
@subject.date.utc.strftime("%Y-%m-%d %H:%M").should == '2013-01-14 21:02'
|
61
84
|
@subject.location.should == "Q's Lab"
|
62
85
|
end
|
63
86
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
describe "cloning configuration data for KVO" do
|
2
|
+
class KVOObservable
|
3
|
+
include MotionModel::Model
|
4
|
+
include MotionModel::ArrayModelAdapter
|
5
|
+
|
6
|
+
columns :name, :nickname
|
7
|
+
end
|
8
|
+
|
9
|
+
class KVOWatcher
|
10
|
+
attr_reader :o
|
11
|
+
|
12
|
+
include BW::KVO
|
13
|
+
|
14
|
+
def initialize(o)
|
15
|
+
@o = o
|
16
|
+
observe(o, :name) do |old_value, new_value|
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
before do
|
22
|
+
@observable = KVOObservable.create!(name: 'Jim', nickname: 'Jimmy')
|
23
|
+
@watcher = KVOWatcher.new(@observable)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "is a KVO anonymous class" do
|
27
|
+
@watcher.o.class.to_s.should.match(/^NSKVO/)
|
28
|
+
@watcher.o.class.should.not == KVOObservable
|
29
|
+
end
|
30
|
+
|
31
|
+
it "retrieves attribute values correctly" do
|
32
|
+
@watcher.o.name.should == @observable.name
|
33
|
+
@watcher.o.nickname.should == @observable.nickname
|
34
|
+
end
|
35
|
+
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.
|
4
|
+
version: 0.4.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-06-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bubble-wrap
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - '='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 1.3.0
|
21
|
+
version: 1.3.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - '='
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 1.3.0
|
29
|
+
version: 1.3.0
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: motion-support
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,18 +59,20 @@ files:
|
|
59
59
|
- Rakefile
|
60
60
|
- app/app_delegate.rb
|
61
61
|
- lib/motion_model.rb
|
62
|
-
- lib/motion_model/
|
63
|
-
-
|
64
|
-
-
|
65
|
-
-
|
66
|
-
-
|
67
|
-
-
|
68
|
-
-
|
69
|
-
-
|
70
|
-
-
|
71
|
-
-
|
72
|
-
-
|
73
|
-
-
|
62
|
+
- lib/motion_model/date_parser.rb
|
63
|
+
- motion/adapters/array_finder_query.rb
|
64
|
+
- motion/adapters/array_model_adapter.rb
|
65
|
+
- motion/adapters/array_model_persistence.rb
|
66
|
+
- motion/date_parser.rb
|
67
|
+
- motion/ext.rb
|
68
|
+
- motion/input_helpers.rb
|
69
|
+
- motion/model/column.rb
|
70
|
+
- motion/model/formotion.rb
|
71
|
+
- motion/model/model.rb
|
72
|
+
- motion/model/model_casts.rb
|
73
|
+
- motion/model/transaction.rb
|
74
|
+
- motion/validatable.rb
|
75
|
+
- motion/version.rb
|
74
76
|
- motion_model.gemspec
|
75
77
|
- spec/adapter_spec.rb
|
76
78
|
- spec/array_model_persistence_spec.rb
|
@@ -80,6 +82,7 @@ files:
|
|
80
82
|
- spec/ext_spec.rb
|
81
83
|
- spec/finder_spec.rb
|
82
84
|
- spec/formotion_spec.rb
|
85
|
+
- spec/kvo_config_clone_spec.rb
|
83
86
|
- spec/model_casting_spec.rb
|
84
87
|
- spec/model_hook_spec.rb
|
85
88
|
- spec/model_spec.rb
|
@@ -120,6 +123,7 @@ test_files:
|
|
120
123
|
- spec/ext_spec.rb
|
121
124
|
- spec/finder_spec.rb
|
122
125
|
- spec/formotion_spec.rb
|
126
|
+
- spec/kvo_config_clone_spec.rb
|
123
127
|
- spec/model_casting_spec.rb
|
124
128
|
- spec/model_hook_spec.rb
|
125
129
|
- spec/model_spec.rb
|