motion_migrate 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +17 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +152 -0
  6. data/Rakefile +3 -0
  7. data/lib/motion_migrate/generate.rb +51 -0
  8. data/lib/motion_migrate/model.rb +10 -0
  9. data/lib/motion_migrate/motion_generate/entity.rb +15 -0
  10. data/lib/motion_migrate/motion_generate/io.rb +79 -0
  11. data/lib/motion_migrate/motion_generate/parser.rb +27 -0
  12. data/lib/motion_migrate/motion_generate/property.rb +171 -0
  13. data/lib/motion_migrate/motion_generate/relationship.rb +153 -0
  14. data/lib/motion_migrate/motion_model/property.rb +14 -0
  15. data/lib/motion_migrate/motion_model/relationship.rb +20 -0
  16. data/lib/motion_migrate.rb +20 -0
  17. data/lib/tasks/migrate.rake +53 -0
  18. data/lib/version.rb +3 -0
  19. data/motion_migrate.gemspec +21 -0
  20. data/spec/property_spec.rb +113 -0
  21. data/spec/relationship_spec.rb +54 -0
  22. data/spec_project/.gitignore +18 -0
  23. data/spec_project/Gemfile +6 -0
  24. data/spec_project/Rakefile +14 -0
  25. data/spec_project/app/app_delegate.rb +7 -0
  26. data/spec_project/app/models/pilot.rb +7 -0
  27. data/spec_project/app/models/plane.rb +11 -0
  28. data/spec_project/db/schema.xcdatamodeld/.xccurrentversion +8 -0
  29. data/spec_project/db/schema.xcdatamodeld/schema.1.xcdatamodel/contents +6 -0
  30. data/spec_project/db/schema.xcdatamodeld/schema.10.xcdatamodel/contents +18 -0
  31. data/spec_project/db/schema.xcdatamodeld/schema.2.xcdatamodel/contents +7 -0
  32. data/spec_project/db/schema.xcdatamodeld/schema.3.xcdatamodel/contents +7 -0
  33. data/spec_project/db/schema.xcdatamodeld/schema.4.xcdatamodel/contents +8 -0
  34. data/spec_project/db/schema.xcdatamodeld/schema.5.xcdatamodel/contents +9 -0
  35. data/spec_project/db/schema.xcdatamodeld/schema.6.xcdatamodel/contents +13 -0
  36. data/spec_project/db/schema.xcdatamodeld/schema.7.xcdatamodel/contents +14 -0
  37. data/spec_project/db/schema.xcdatamodeld/schema.8.xcdatamodel/contents +16 -0
  38. data/spec_project/db/schema.xcdatamodeld/schema.9.xcdatamodel/contents +16 -0
  39. data/spec_project/spec/helper.rb +8 -0
  40. data/spec_project/spec/property_spec.rb +57 -0
  41. data/spec_project/spec/relationship_spec.rb +105 -0
  42. data/spec_project/vendor/Podfile.lock +11 -0
  43. metadata +113 -0
@@ -0,0 +1,153 @@
1
+ module MotionMigrate
2
+ module MotionGenerate
3
+ module Relationship
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def belongs_to(name, options={})
10
+ relationship(name, :belongs_to, options)
11
+ end
12
+
13
+ def has_many(name, options={})
14
+ relationship(name, :has_many, options)
15
+ end
16
+
17
+ def relationship(name, type, options={})
18
+ options.each { |key, value| raise_if_relationship_option_not_allowed(type, key) }
19
+
20
+ raise_if_relationship_options_missing(options)
21
+ raise_if_deletion_rule_not_allowed(options)
22
+
23
+ attributes = {
24
+ name: name,
25
+ optional: core_data_boolean(true),
26
+ deletionRule: core_data_string(:no_action),
27
+ syncable: core_data_boolean(true)
28
+ }
29
+ attributes.merge!({ minCount: 1, maxCount: 1 }) if type == :belongs_to
30
+ attributes.merge!(core_data_relationship_attributes(type, options))
31
+ relationships[self.entity_name] = {} if relationships[self.entity_name].nil?
32
+ relationships[self.entity_name][name] = attributes
33
+ attributes
34
+ end
35
+
36
+ def relationships
37
+ @@relationships ||= {}
38
+ end
39
+
40
+ def raise_if_relationship_option_not_allowed(type, option)
41
+ unless relationship_option_allowed?(type, option)
42
+ raise <<-ERROR
43
+ ! The option must be one of the following:
44
+ !
45
+ ! For type :belongs_to:
46
+ ! :required
47
+ ! :deletion_rule
48
+ ! :class_name
49
+ ! :inverse_of
50
+ ! :spotlight
51
+ ! :truth_file
52
+ ! :transient
53
+ !
54
+ ! For type :has_many:
55
+ ! :required
56
+ ! :min
57
+ ! :max
58
+ ! :deletion_rule
59
+ ! :class_name
60
+ ! :inverse_of
61
+ ! :ordered
62
+ ! :spotlight
63
+ ! :truth_file
64
+ ! :transient
65
+ ERROR
66
+ end
67
+ end
68
+
69
+ def raise_if_relationship_options_missing(options)
70
+ unless relationship_options_complete?(options)
71
+ raise <<-ERROR
72
+ ! One of these options are required:
73
+ ! :class_name
74
+ ! :inverse_of
75
+ ERROR
76
+ end
77
+ end
78
+
79
+ def deletion_rule_allowed?(options)
80
+ allowed_deletion_rules = [
81
+ :nullify,
82
+ :cascade,
83
+ :deny
84
+ ]
85
+ !options[:deletion_rule] || allowed_deletion_rules.include?(options[:deletion_rule])
86
+ end
87
+
88
+ def raise_if_deletion_rule_not_allowed(options)
89
+ unless deletion_rule_allowed?(options)
90
+ raise <<-ERROR
91
+ ! One of these deletion rules are allowed:
92
+ ! :nullify
93
+ ! :cascade
94
+ ! :deny
95
+ ERROR
96
+ end
97
+ end
98
+
99
+ def relationship_options_complete?(options)
100
+ required_options = [:class_name, :inverse_of]
101
+ (required_options.uniq - options.keys.uniq).empty?
102
+ end
103
+
104
+ def relationship_option_allowed?(type, option)
105
+ allowed_options = {
106
+ has_many: [:ordered, :min, :max],
107
+ }[type] || []
108
+
109
+ allowed_options += [
110
+ :required,
111
+ :spotlight,
112
+ :truth_file,
113
+ :transient,
114
+ :inverse_of,
115
+ :class_name,
116
+ :deletion_rule
117
+ ]
118
+ allowed_options.include?(option)
119
+ end
120
+
121
+ def core_data_relationship_attributes(type, options)
122
+ attributes = {}
123
+
124
+ options.each do |key, value|
125
+ case key
126
+ when :required
127
+ attributes[:optional] = core_data_boolean(value != true)
128
+ when :inverse_of
129
+ attributes[:inverseName] = value
130
+ when :class_name
131
+ attributes[:inverseEntity] = value
132
+ attributes[:destinationEntity] = value
133
+ when :deletion_rule
134
+ attributes[:deletionRule] = core_data_string(value)
135
+ when :transient
136
+ attributes[:transient] = core_data_boolean(value)
137
+ when :spotlight
138
+ attributes[:spotlightIndexingEnabled] = core_data_boolean(value)
139
+ when :truth_file
140
+ attributes[:storedInTruthFile] = core_data_boolean(value)
141
+ when :min
142
+ attributes[:minCount] = value
143
+ when :max
144
+ attributes[:maxCount] = value
145
+ end
146
+ end
147
+ attributes[:toMany] = core_data_boolean(type == :has_many)
148
+ attributes
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,14 @@
1
+ module MotionMigrate
2
+ module MotionModel
3
+ module Property
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def property(name, type, options={})
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ module MotionMigrate
2
+ module MotionModel
3
+ module Relationship
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def belongs_to(name, options={})
10
+ end
11
+
12
+ def has_many(name, options={})
13
+ end
14
+
15
+ def belongs_to(name, options={})
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ unless defined?(Motion::Project::Config)
2
+ raise "This file must be required within a RubyMotion project Rakefile."
3
+ end
4
+
5
+ Motion::Project::App.setup do |app|
6
+ Dir.glob(File.join(File.dirname(__FILE__), "motion_migrate/motion_model/**/*.rb")).each do |file|
7
+ app.files.unshift(file)
8
+ end
9
+ app.files.unshift(File.join(File.dirname(__FILE__), 'motion_migrate/model.rb'))
10
+
11
+ app.frameworks += ['CoreData'] unless app.frameworks.include?("CoreData")
12
+ end
13
+
14
+ module MotionMigrate
15
+ end
16
+
17
+ # Include rake files
18
+ Dir.glob(File.join(File.dirname(__FILE__), "tasks/**/*.rake")) do |task|
19
+ import task
20
+ end
@@ -0,0 +1,53 @@
1
+ require "nokogiri"
2
+ require 'fileutils'
3
+
4
+ require "motion_migrate/motion_generate/entity"
5
+ require "motion_migrate/motion_generate/parser"
6
+ require "motion_migrate/motion_generate/property"
7
+ require "motion_migrate/motion_generate/relationship"
8
+ require "motion_migrate/generate"
9
+ require "motion_migrate/motion_generate/io"
10
+
11
+ namespace :db do
12
+ desc "Generate a version of the current database model as described in the models."
13
+ task :migrate do
14
+ schema_xml = MotionMigrate::Generate.build
15
+ MotionMigrate::IO.write(schema_xml)
16
+ end
17
+
18
+ desc "Go back to the previous version of the database model."
19
+ task :rollback do
20
+ if version = MotionMigrate::IO.current_schema_version
21
+ if version == 1
22
+ puts "! Can't rollback schema when version is 1."
23
+ else
24
+ schema = MotionMigrate::IO.current_schema
25
+ MotionMigrate::IO.write_current_schema(version - 1)
26
+ FileUtils.rm_rf(schema)
27
+ puts "# Data model rolled back to version #{version - 1}."
28
+ end
29
+ else
30
+ puts "! No schema found in this project."
31
+ end
32
+ end
33
+
34
+ namespace :schema do
35
+ desc "Dump the current version of the database model scheme."
36
+ task :dump do
37
+ if current_schema = MotionMigrate::IO.current_schema
38
+ puts File.open(File.join(current_schema, "contents")).read
39
+ else
40
+ puts "! No schema found in this project."
41
+ end
42
+ end
43
+
44
+ desc "Show the current version of the database model scheme."
45
+ task :version do
46
+ if version = MotionMigrate::IO.current_schema_version
47
+ puts "# Data model is currently at version #{version}."
48
+ else
49
+ puts "! No schema found in this project."
50
+ end
51
+ end
52
+ end
53
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module MotionMigrate
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "motion_migrate"
8
+ gem.version = MotionMigrate::VERSION
9
+ gem.authors = ["Jelle Vandebeeck"]
10
+ gem.email = ["jelle@fousa.be"]
11
+ gem.description = %q{Generate the Core Data model from your RubyMotion code.}
12
+ gem.summary = %q{Generate the Core Data model from your RubyMotion code. Never open XCode again!}
13
+ gem.homepage = "http://fousa.github.com/motion_migrate/"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_development_dependency 'rake'
20
+ gem.add_dependency 'nokogiri'
21
+ end
@@ -0,0 +1,113 @@
1
+ require 'date'
2
+
3
+ require "motion_migrate/motion_generate/entity"
4
+ require "motion_migrate/motion_generate/parser"
5
+ require "motion_migrate/motion_generate/property"
6
+ require "motion_migrate/motion_generate/relationship"
7
+ require "motion_migrate/generate"
8
+
9
+ module MotionMigrate
10
+ describe "property definitions in the model" do
11
+ before do
12
+ @allowed_options = {
13
+ required: true,
14
+ transient: true,
15
+ indexed: true,
16
+ spotlight: true,
17
+ truth_file: true
18
+ }
19
+ @filled_allowed_attributes = {
20
+ optional: "NO",
21
+ transient: "YES",
22
+ indexed: "YES",
23
+ spotlightIndexingEnabled: "YES",
24
+ storedInTruthFile: "YES",
25
+ syncable: "YES"
26
+ }
27
+ end
28
+
29
+ %w(string integer_16 integer_32 integer_64 decimal double float date binary_data boolean).each do |type|
30
+ it "should be able to define #{type}" do
31
+ type_string = type.split("_").each{|word| word.capitalize! }.join(" ")
32
+ type_string = "Binary" if type == "binary_data"
33
+ MotionMigrate::Model.property(:field, type).should == { :name => :field,
34
+ :attributeType => type_string,
35
+ :optional=>"YES",
36
+ :syncable => "YES" }
37
+ end
38
+ end
39
+
40
+ it "should error on undiffened types" do
41
+ lambda { MotionMigrate::Model.property(:field, :unknown) }.should raise_error
42
+ end
43
+
44
+ %w(string integer_16 integer_32 integer_64 decimal double float date binary_data boolean).each do |type|
45
+ it "should error on undiffened options for #{type}" do
46
+ lambda { MotionMigrate::Model.property(:field, type, :unknown => true) }.should raise_error
47
+ end
48
+ end
49
+
50
+ it "should return the correct options for string" do
51
+ options = @allowed_options.merge({
52
+ min: "1",
53
+ max: "10",
54
+ default: "motion",
55
+ regex: "/we/"
56
+ })
57
+ MotionMigrate::Model.property(:field, :string, options).should == @filled_allowed_attributes.merge({ :name => :field,
58
+ :attributeType => "String",
59
+ :minValueString => "1",
60
+ :maxValueString => "10",
61
+ :defaultValueString => "motion",
62
+ :regularExpressionString => "/we/" })
63
+ end
64
+
65
+ %w(integer_16 integer_32 integer_64 decimal double float).each do |type|
66
+ it "should return the correct options for #{type}" do
67
+ options = @allowed_options.merge({
68
+ min: "1",
69
+ max: "10",
70
+ default: "5"
71
+ })
72
+ MotionMigrate::Model.property(:field, type, options).should == @filled_allowed_attributes.merge({ :name => :field,
73
+ :attributeType => type.split("_").each{|word| word.capitalize! }.join(" "),
74
+ :minValueString => "1",
75
+ :maxValueString => "10",
76
+ :defaultValueString => "5" })
77
+ end
78
+ end
79
+
80
+ it "should return the correct options for boolean" do
81
+ options = @allowed_options.merge({
82
+ default: true
83
+ })
84
+ MotionMigrate::Model.property(:field, :boolean, options).should == @filled_allowed_attributes.merge({ :name => :field,
85
+ :attributeType => "Boolean",
86
+ :defaultValueString => "YES" })
87
+ end
88
+
89
+ it "should return the correct options for binary_data" do
90
+ options = @allowed_options.merge({
91
+ external_storage: true
92
+ })
93
+ MotionMigrate::Model.property(:field, :binary_data, options).should == @filled_allowed_attributes.merge({ :name => :field,
94
+ :attributeType => "Binary",
95
+ :allowsExternalBinaryDataStorage => "YES" })
96
+ end
97
+
98
+ it "should return the correct options for date" do
99
+ options = @allowed_options.merge({
100
+ min: DateTime.new(2013, 1, 1, 12, 0, 0),
101
+ max: DateTime.new(2013, 1, 31, 12, 0, 0),
102
+ default: DateTime.new(2013, 1, 5, 12, 0, 0)
103
+ })
104
+ MotionMigrate::Model.property(:field, :date, options).should == @filled_allowed_attributes.merge({ :name => :field,
105
+ :attributeType => "Date",
106
+ :minDateTimeInterval => "378738000",
107
+ :maxDateTimeInterval => "381330000",
108
+ :defaultDateTimeInterval => "379083600" })
109
+ end
110
+ end
111
+ end
112
+
113
+ # Test all types + properties
@@ -0,0 +1,54 @@
1
+ require "motion_migrate/motion_generate/entity"
2
+ require "motion_migrate/motion_generate/parser"
3
+ require "motion_migrate/motion_generate/property"
4
+ require "motion_migrate/motion_generate/relationship"
5
+ require "motion_migrate/generate"
6
+
7
+ module MotionMigrate
8
+ describe "relationship definitions in the model" do
9
+ before do
10
+ @allowed_options = {
11
+ required: true,
12
+ transient: true,
13
+ class_name: "Test",
14
+ inverse_of: "test",
15
+ deletion_rule: :cascade,
16
+ spotlight: true,
17
+ truth_file: true
18
+ }
19
+ @filled_allowed_attributes = {
20
+ optional: "NO",
21
+ transient: "YES",
22
+ inverseEntity: "Test",
23
+ destinationEntity: "Test",
24
+ inverseName: "test",
25
+ deletionRule: "Cascade",
26
+ spotlightIndexingEnabled: "YES",
27
+ storedInTruthFile: "YES",
28
+ syncable: "YES"
29
+ }
30
+ end
31
+
32
+ %w(belongs_to has_many).each do |type|
33
+ it "should error on incomplete options for #{type}" do
34
+ lambda { MotionMigrate::Model.send(type, :field) }.should raise_error
35
+ end
36
+
37
+ it "should error on undiffened options for #{type}" do
38
+ lambda { MotionMigrate::Model.send(type, :field, :class_name => "Test", :inverse_of => "test", :unknown => true) }.should raise_error
39
+ end
40
+
41
+ it "should error on undiffened deletion rules for #{type}" do
42
+ lambda { MotionMigrate::Model.send(type, :field, :class_name => "Test", :inverse_of => "test", :deletion_rule => :unknown) }.should raise_error
43
+ end
44
+ end
45
+
46
+ it "should return the correct options for belongs_to" do
47
+ MotionMigrate::Model.belongs_to(:field, @allowed_options).should == @filled_allowed_attributes.merge({ :toMany => "NO", :name => :field, :minCount => 1, :maxCount => 1 })
48
+ end
49
+
50
+ it "should return the correct options for has_many" do
51
+ MotionMigrate::Model.has_many(:field, @allowed_options).should == @filled_allowed_attributes.merge({ :toMany => "YES", :name => :field })
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,18 @@
1
+ .repl_history
2
+ build
3
+ tags
4
+ app/pixate_code.rb
5
+ resources/*.nib
6
+ resources/*.momd
7
+ resources/*.storyboardc
8
+ .DS_Store
9
+ nbproject
10
+ .redcar
11
+ #*#
12
+ *~
13
+ *.sw[po]
14
+ .eprj
15
+ .sass-cache
16
+ .idea
17
+
18
+ /vendor/Pods/
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+
3
+ gem "rake", "0.9.4"
4
+ gem "motion_migrate", :path => "../../."
5
+ gem 'cocoapods'
6
+ gem "motion-cocoapods", "1.2.1"
@@ -0,0 +1,14 @@
1
+ # -*- coding: utf-8 -*-
2
+ $:.unshift("/Library/RubyMotion/lib")
3
+ require 'motion/project'
4
+
5
+ require 'bundler'
6
+ Bundler.require
7
+
8
+ Motion::Project::App.setup do |app|
9
+ app.name = 'Motion Migrate Spec Project'
10
+
11
+ app.pods do
12
+ pod 'MagicalRecord', '2.1'
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ class AppDelegate
2
+ def application(application, didFinishLaunchingWithOptions:launchOptions)
3
+ MagicalRecord.setupCoreDataStackWithAutoMigratingSqliteStoreNamed("MotionMigrateSpec.sqlite")
4
+
5
+ true
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ class Pilot < MotionMigrate::Model
2
+ property :name, :string
3
+
4
+ belongs_to :plane, :class_name => "Plane", :inverse_of => :pilot
5
+ has_many :owned_planes, :class_name => "Plane", :inverse_of => :owner
6
+ has_many :flown_planes, :class_name => "Plane", :inverse_of => :flown_by_pilots
7
+ end
@@ -0,0 +1,11 @@
1
+ class Plane < MotionMigrate::Model
2
+ property :name, :string
3
+ property :multi_engine, :boolean, :default => false
4
+ property :first_flight_at, :date
5
+ property :flight_info, :binary_data
6
+
7
+ belongs_to :pilot, :class_name => "Pilot", :inverse_of => :plane
8
+ belongs_to :owner, :class_name => "Pilot", :inverse_of => :owned_planes
9
+
10
+ has_many :flown_by_pilots, :class_name => "Pilot", :inverse_of => :flown_planes
11
+ end
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>_XCCurrentVersionName</key>
6
+ <string>schema.10.xcdatamodel</string>
7
+ </dict>
8
+ </plist>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1811" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
3
+ <entity name="Plane" representedClassName="Plane" syncable="YES">
4
+ <attribute name="name" attributeType="String" optional="YES" syncable="YES"/>
5
+ </entity>
6
+ </model>
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1811" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
3
+ <entity name="Pilot" representedClassName="Pilot" syncable="YES">
4
+ <attribute name="name" attributeType="String" optional="YES" syncable="YES"/>
5
+ <relationship name="plane" optional="YES" deletionRule="No Action" syncable="YES" minCount="1" maxCount="1" inverseEntity="Plane" destinationEntity="Plane" inverseName="pilot" toMany="NO"/>
6
+ <relationship name="owned_planes" optional="YES" deletionRule="No Action" syncable="YES" inverseEntity="Plane" destinationEntity="Plane" inverseName="owner" toMany="YES"/>
7
+ <relationship name="flown_planes" optional="YES" deletionRule="No Action" syncable="YES" inverseEntity="Plane" destinationEntity="Plane" inverseName="flown_by_pilots" toMany="YES"/>
8
+ </entity>
9
+ <entity name="Plane" representedClassName="Plane" syncable="YES">
10
+ <attribute name="name" attributeType="String" optional="YES" syncable="YES"/>
11
+ <attribute name="multi_engine" attributeType="Boolean" optional="YES" syncable="YES" defaultValueString="NO"/>
12
+ <attribute name="first_flight_at" attributeType="Date" optional="YES" syncable="YES"/>
13
+ <attribute name="flight_info" attributeType="Binary" optional="YES" syncable="YES"/>
14
+ <relationship name="pilot" optional="YES" deletionRule="No Action" syncable="YES" minCount="1" maxCount="1" inverseEntity="Pilot" destinationEntity="Pilot" inverseName="plane" toMany="NO"/>
15
+ <relationship name="owner" optional="YES" deletionRule="No Action" syncable="YES" minCount="1" maxCount="1" inverseEntity="Pilot" destinationEntity="Pilot" inverseName="owned_planes" toMany="NO"/>
16
+ <relationship name="flown_by_pilots" optional="YES" deletionRule="No Action" syncable="YES" inverseEntity="Pilot" destinationEntity="Pilot" inverseName="flown_planes" toMany="YES"/>
17
+ </entity>
18
+ </model>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1811" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
3
+ <entity name="Plane" representedClassName="Plane" syncable="YES">
4
+ <attribute name="name" attributeType="String" optional="YES" syncable="YES"/>
5
+ <attribute name="multi_engine" attributeType="Boolean" optional="YES" syncable="YES"/>
6
+ </entity>
7
+ </model>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1811" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
3
+ <entity name="Plane" representedClassName="Plane" syncable="YES">
4
+ <attribute name="name" attributeType="String" optional="YES" syncable="YES"/>
5
+ <attribute name="multi_engine" attributeType="Boolean" optional="YES" syncable="YES" defaultValueString="NO"/>
6
+ </entity>
7
+ </model>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1811" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
3
+ <entity name="Plane" representedClassName="Plane" syncable="YES">
4
+ <attribute name="name" attributeType="String" optional="YES" syncable="YES"/>
5
+ <attribute name="multi_engine" attributeType="Boolean" optional="YES" syncable="YES" defaultValueString="NO"/>
6
+ <attribute name="first_flight_at" attributeType="Date" optional="YES" syncable="YES"/>
7
+ </entity>
8
+ </model>
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1811" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
3
+ <entity name="Plane" representedClassName="Plane" syncable="YES">
4
+ <attribute name="name" attributeType="String" optional="YES" syncable="YES"/>
5
+ <attribute name="multi_engine" attributeType="Boolean" optional="YES" syncable="YES" defaultValueString="NO"/>
6
+ <attribute name="first_flight_at" attributeType="Date" optional="YES" syncable="YES"/>
7
+ <attribute name="flight_info" attributeType="Binary" optional="YES" syncable="YES"/>
8
+ </entity>
9
+ </model>
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1811" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
3
+ <entity name="Plane" representedClassName="Plane" syncable="YES">
4
+ <attribute name="name" attributeType="String" optional="YES" syncable="YES"/>
5
+ <attribute name="multi_engine" attributeType="Boolean" optional="YES" syncable="YES" defaultValueString="NO"/>
6
+ <attribute name="first_flight_at" attributeType="Date" optional="YES" syncable="YES"/>
7
+ <attribute name="flight_info" attributeType="Binary" optional="YES" syncable="YES"/>
8
+ <relationship name="pilot" optional="YES" deletionRule="No Action" syncable="YES" minCount="1" maxCount="1" inverseEntity="Pilot" destinationEntity="Pilot" inverseName="plane" toMany="NO"/>
9
+ </entity>
10
+ <entity name="Pilot" representedClassName="Pilot" syncable="YES">
11
+ <relationship name="plane" optional="YES" deletionRule="No Action" syncable="YES" minCount="1" maxCount="1" inverseEntity="Plane" destinationEntity="Plane" inverseName="pilot" toMany="NO"/>
12
+ </entity>
13
+ </model>
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1811" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
3
+ <entity name="Pilot" representedClassName="Pilot" syncable="YES">
4
+ <attribute name="name" attributeType="String" optional="YES" syncable="YES"/>
5
+ <relationship name="plane" optional="YES" deletionRule="No Action" syncable="YES" minCount="1" maxCount="1" inverseEntity="Plane" destinationEntity="Plane" inverseName="pilot" toMany="NO"/>
6
+ </entity>
7
+ <entity name="Plane" representedClassName="Plane" syncable="YES">
8
+ <attribute name="name" attributeType="String" optional="YES" syncable="YES"/>
9
+ <attribute name="multi_engine" attributeType="Boolean" optional="YES" syncable="YES" defaultValueString="NO"/>
10
+ <attribute name="first_flight_at" attributeType="Date" optional="YES" syncable="YES"/>
11
+ <attribute name="flight_info" attributeType="Binary" optional="YES" syncable="YES"/>
12
+ <relationship name="pilot" optional="YES" deletionRule="No Action" syncable="YES" minCount="1" maxCount="1" inverseEntity="Pilot" destinationEntity="Pilot" inverseName="plane" toMany="NO"/>
13
+ </entity>
14
+ </model>