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
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in motion_migrate.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jelle Vandebeeck
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # Motion Migrate
2
+
3
+ Generate the Core Data model from your RubyMotion code. Never open XCode again!
4
+
5
+ ## Why
6
+
7
+ I love the [Nitron](https://github.com/mattgreen/nitron) gem created by [@mattgreen](https://github.com/mattgreen/). But I missed some features, and I really hate to open Xcode to create the Core Data model. So I created some rake tasks that helped me generate the Core Data model. Thanks to [@mattgreen](https://github.com/mattgreen/) for starting this in the 0.3 branch of the [Nitron](https://github.com/mattgreen/nitron) project.
8
+
9
+ Another reason why I wanted to create this gem is because I love using [Magical Record](https://github.com/magicalpanda/MagicalRecord). This is really a great way to handle Core Data, it's like a small layer on top of it.
10
+
11
+ Most gems that want to --railsify-- Core Data don't make correct use of the different contexts as used by Core Data. So I wanted to be able to use Core Data as it should, by just generating the model.
12
+
13
+ Maybe adding some simplicity in creating the relationships between models will be added later. But for now, generating the Core Data model is the main goal!
14
+
15
+ ## Installation
16
+
17
+ The installation is simple. Just add this line to you Gemfile:
18
+
19
+ gem 'motion_migrate'
20
+
21
+ Execute:
22
+
23
+ $ bundle install
24
+
25
+ And BOOM, you're on your way!
26
+
27
+ ## Usage
28
+
29
+ With this gem you can set every option you can while using the Xcode modelling tool. I'm not going to describe every option because I assume RubyMotion developers know how Core Data works. Here is a short list of the available options for a property:
30
+
31
+ - min
32
+ - max
33
+ - default
34
+ - regex
35
+ - external_storage
36
+ - required
37
+ - transient
38
+ - indexed
39
+ - spotlight
40
+ - truth_file
41
+
42
+ And of course here are the available options for the relationships (belongs to and has many):
43
+
44
+ - required
45
+ - deletion_rule
46
+ - class_name
47
+ - spotlight
48
+ - truth_file
49
+ - transient
50
+ - min
51
+ - max
52
+ - inverse_of
53
+ - ordered
54
+
55
+ The 'spec_project' is an example project with two models containing properties and relationships.
56
+
57
+ But the main question is how to generate this Core Data model. Well start by extending your models from MotionMigrate::Model. This class is extended from NSManagedObject so you're ready to continue with Core Data once the
58
+ model is generated.
59
+
60
+ class Plane < MotionMigrate::Model
61
+ end
62
+
63
+ Next define the properties:
64
+ This will generate two properties, a name property and a multi property. To add some relationships to it, you can add a belongs\_to -- rails-like-shizzle-- to the model.
65
+
66
+ class Plane < MotionMigrate::Model
67
+ property :name, :string
68
+ property :multi, :boolean, :default => false
69
+
70
+ belongs_to :pilot, :class_name => "Pilot", :inverse_of => :planes
71
+ end
72
+
73
+ Don't forget to add a has\_many or belongs\_to as a reverse relationship.
74
+
75
+ class Pilot < MotionMigrate::Model
76
+ has_many :planes, :class_name => "Plane", :inverse_of => :pilot
77
+ end
78
+
79
+ The relationships as defined above contain the minimal parameters you'll have to pass to the has\_many or belongs\_to.
80
+
81
+ Now the most important part, migrating the model. Just run this command to generate the Core Data model from the current models.
82
+
83
+ $ rake db:migrate
84
+
85
+ You can also revert to a previous version of the Core Data model by running:
86
+
87
+ $ rake db:rollback
88
+
89
+ You can check out the other rake tasks by running:
90
+
91
+ $ rake -T
92
+
93
+ ## Example
94
+
95
+ But for me, the most obvious part is an small example application that shows you how this gem is used. Check out the spec_project en run it so you can see it in action.
96
+
97
+ ## Todo
98
+
99
+ This is certainly not the end, still got a lot to do.
100
+
101
+ - [ ] Better version generation. (shouldn't always generate a new version unless told to do so)
102
+ - [ ] Clean up the utility methods.
103
+ - [ ] Implement [mogenerator](https://github.com/rentzsch/mogenerator) functionality.
104
+ - [ ] Add << functionatlity to the relationships in orde to add objects.
105
+ - [ ] Try to handle relationships in a more Ruby on Railzy way.
106
+
107
+ ## Tests
108
+
109
+ When contributing to this project make sure you run the tests to make sure everything keeps working like it should.
110
+
111
+ Run the migration tests by executing this command from the project root:
112
+
113
+ $ rspec
114
+
115
+ When you want to test the functional integration with [Magical Record](https://github.com/magicalpanda/MagicalRecord) by moving to the spec_project folder and running the specs.
116
+
117
+ $ rake spec
118
+
119
+ ## Contributing
120
+
121
+ It would be awesome if you contribute!
122
+
123
+ 1. Fork it
124
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
125
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
126
+ 4. Push to the branch (`git push origin my-new-feature`)
127
+ 5. Create new Pull Request
128
+
129
+ ## License
130
+
131
+ Copyright (c) 2013 Jelle Vandebeeck
132
+
133
+ MIT License
134
+
135
+ Permission is hereby granted, free of charge, to any person obtaining
136
+ a copy of this software and associated documentation files (the
137
+ "Software"), to deal in the Software without restriction, including
138
+ without limitation the rights to use, copy, modify, merge, publish,
139
+ distribute, sublicense, and/or sell copies of the Software, and to
140
+ permit persons to whom the Software is furnished to do so, subject to
141
+ the following conditions:
142
+
143
+ The above copyright notice and this permission notice shall be
144
+ included in all copies or substantial portions of the Software.
145
+
146
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
147
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
148
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
149
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
150
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
151
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
152
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
@@ -0,0 +1,51 @@
1
+ module MotionMigrate
2
+ class Model
3
+ include MotionMigrate::MotionGenerate::Entity
4
+ include MotionMigrate::MotionGenerate::Parser
5
+ include MotionMigrate::MotionGenerate::Property
6
+ include MotionMigrate::MotionGenerate::Relationship
7
+ end
8
+
9
+ class Generate
10
+ class << self
11
+ def build
12
+ models = Dir.glob("app/models/*.rb")
13
+ raise "! No models defined in 'app/models', add models to this folder if you want to generate the database model." if models.count == 0
14
+
15
+ models.each do |filename|
16
+ File.open(filename) { |file| eval(file.read) }
17
+ end
18
+
19
+ builder = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
20
+ xml.model(database_model_attributes) do
21
+ ObjectSpace.each_object(Class).select { |klass| klass < MotionMigrate::Model }.each do |entity|
22
+ xml.entity(:name => entity.entity_name, :representedClassName => entity.entity_name, :syncable => "YES") do
23
+ entity.properties[entity.entity_name].each do |name, property|
24
+ xml.attribute(property)
25
+ end unless entity.properties[entity.entity_name].nil?
26
+ entity.relationships[entity.entity_name].each do |name, relationship|
27
+ xml.relationship(relationship)
28
+ end unless entity.relationships[entity.entity_name].nil?
29
+ end
30
+ end
31
+ end
32
+ end
33
+ builder.to_xml
34
+ end
35
+
36
+ def database_model_attributes
37
+ {
38
+ :name => "",
39
+ :userDefinedModelVersionIdentifier => "",
40
+ :type => "com.apple.IDECoreDataModeler.DataModel",
41
+ :documentVersion => "1.0",
42
+ :lastSavedToolsVersion => "1811",
43
+ :systemVersion => "11D50",
44
+ :minimumToolsVersion => "Automatic",
45
+ :macOSVersion => "Automatic",
46
+ :iOSVersion => "Automatic"
47
+ }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ module MotionMigrate
2
+ class Model < NSManagedObject
3
+ include MotionMigrate::MotionModel::Property
4
+ include MotionMigrate::MotionModel::Relationship
5
+
6
+ def inspect
7
+ # Show entity with fields
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module MotionMigrate
2
+ module MotionGenerate
3
+ module Entity
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def entity_name
10
+ name.split("::").last
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,79 @@
1
+ module MotionMigrate
2
+ class IO
3
+ class << self
4
+ def write(xml="")
5
+ create_db
6
+
7
+ current_schema = nil
8
+ if version = current_schema_version()
9
+ File.open(File.join(current_schema(), "contents")) do |f|
10
+ current_schema = f.read
11
+ end
12
+ end
13
+
14
+ if current_schema != xml
15
+ version = (version || 0) + 1
16
+
17
+ latest_schema_path = xcdatamodeld_path("schema.#{version}.xcdatamodel")
18
+ Dir.mkdir(latest_schema_path) unless Dir.exists?(latest_schema_path)
19
+
20
+ File.open(File.join(latest_schema_path, "contents"), "w") do |file|
21
+ file.write(xml)
22
+ end
23
+ write_current_schema(version)
24
+
25
+ unless File.symlink?("resources/schema.xcdatamodeld")
26
+ File.symlink("../db/schema.xcdatamodeld", "resources/schema.xcdatamodeld")
27
+ end
28
+
29
+ puts "# Data model migrated to version #{version}."
30
+ else
31
+ length = 38 + version.to_s.length
32
+ puts "# Data model already at version #{version}."
33
+ end
34
+ end
35
+
36
+ def current_schema_version
37
+ return nil unless path = current_schema
38
+
39
+ version = nil
40
+ path.match(/\.([0-9]+)\.xcdatamodel$/) do |match|
41
+ version = match[1].to_i
42
+ end
43
+ version
44
+ end
45
+
46
+ def current_schema
47
+ plist = xcdatamodeld_path(".xccurrentversion")
48
+ return nil unless File.exists?(plist)
49
+
50
+ xcdatamodeld_path(Nokogiri::XML(File.open(plist)).at_xpath("/plist/dict/string").text)
51
+ end
52
+
53
+ def write_current_schema(version)
54
+ File.open(xcdatamodeld_path(".xccurrentversion"), "w") do |file|
55
+ file.write(<<-PLIST)
56
+ <?xml version="1.0" encoding="UTF-8"?>
57
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
58
+ <plist version="1.0">
59
+ <dict>
60
+ <key>_XCCurrentVersionName</key>
61
+ <string>schema.#{version}.xcdatamodel</string>
62
+ </dict>
63
+ </plist>
64
+ PLIST
65
+ end
66
+ end
67
+
68
+ protected
69
+
70
+ def create_db
71
+ FileUtils.mkdir_p(File.join("db", "schema.xcdatamodeld"))
72
+ end
73
+
74
+ def xcdatamodeld_path(*args)
75
+ File.join(["db", "schema.xcdatamodeld"] + args)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,27 @@
1
+ module MotionMigrate
2
+ module MotionGenerate
3
+ module Parser
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def core_data_string(string)
10
+ string.to_s.split("_").each{|word| word.capitalize! }.join(" ")
11
+ end
12
+
13
+ def core_data_variable(value)
14
+ value.to_s.split("_").each{ |word| word.capitalize! }.join("")
15
+ end
16
+
17
+ def core_data_boolean(value)
18
+ value == true ? "YES" : "NO"
19
+ end
20
+
21
+ def core_data_date(value)
22
+ (value.to_time.to_i - Date.new(2001, 1, 1).to_time.to_i).to_s
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,171 @@
1
+ module MotionMigrate
2
+ module MotionGenerate
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
+ type = type.to_sym
11
+
12
+ raise_if_property_type_not_allowed(type)
13
+ options.each { |key, value| raise_if_property_option_not_allowed(type, key) }
14
+
15
+ attribute_type = type == :binary_data ? "Binary" : core_data_string(type)
16
+ attributes = {
17
+ name: name,
18
+ attributeType: attribute_type,
19
+ optional: core_data_boolean(true),
20
+ syncable: core_data_boolean(true)
21
+ }
22
+ attributes.merge!(core_data_property_attributes(type, options))
23
+ properties[self.entity_name] = {} if properties[self.entity_name].nil?
24
+ properties[self.entity_name][name] = attributes
25
+ attributes
26
+ end
27
+
28
+ def properties
29
+ @@properties ||= {}
30
+ end
31
+
32
+ def raise_if_property_type_not_allowed(type)
33
+ unless property_type_allowed?(type)
34
+ raise <<-ERROR
35
+ ! The type must be one of the following:
36
+ ! :string
37
+ ! :integer_16
38
+ ! :integer_32
39
+ ! :integer_64
40
+ ! :decimal
41
+ ! :double
42
+ ! :float
43
+ ! :boolean
44
+ ! :date
45
+ ! :binary_data
46
+ ERROR
47
+ end
48
+ end
49
+
50
+ def property_type_allowed?(type)
51
+ [
52
+ :string,
53
+ :integer_16,
54
+ :integer_32,
55
+ :integer_64,
56
+ :decimal,
57
+ :double,
58
+ :float,
59
+ :boolean,
60
+ :date,
61
+ :binary_data
62
+ ].include?(type)
63
+ end
64
+
65
+ def raise_if_property_option_not_allowed(type, option)
66
+ unless property_option_allowed?(type, option)
67
+ raise <<-ERROR
68
+ ! The option must be one of the following:
69
+ !
70
+ ! For type :string:
71
+ ! :min
72
+ ! :max
73
+ ! :default
74
+ ! :regex
75
+ !
76
+ ! For type :boolean:
77
+ ! :default
78
+ !
79
+ ! For type :date, :integer_16, :integer_32, :integer_64, :decimal, :double or :float:
80
+ ! :min
81
+ ! :max
82
+ ! :default
83
+ !
84
+ ! For type :binary_data:
85
+ ! :external_storage
86
+ !
87
+ ! Options allowed for all types:
88
+ ! :required
89
+ ! :transient
90
+ ! :indexed
91
+ ! :spotlight
92
+ ! :truth_file
93
+ ERROR
94
+ end
95
+ end
96
+
97
+ def property_option_allowed?(type, option)
98
+ type = :number if [
99
+ :integer_16,
100
+ :integer_32,
101
+ :integer_64,
102
+ :decimal,
103
+ :double,
104
+ :float,
105
+ :date
106
+ ].include?(type)
107
+
108
+ allowed_options = {
109
+ number: [:min, :max, :default],
110
+ string: [:min, :max, :default, :regex],
111
+ boolean: [:default],
112
+ binary_data: [:external_storage]
113
+ }[type]
114
+
115
+ allowed_options += [
116
+ :required,
117
+ :transient,
118
+ :indexed,
119
+ :spotlight,
120
+ :truth_file
121
+ ]
122
+ allowed_options.include?(option)
123
+ end
124
+
125
+ def core_data_property_attributes(type, options)
126
+ attributes = {}
127
+
128
+ options.each do |key, value|
129
+ case key
130
+ when :required
131
+ attributes[:optional] = core_data_boolean(value != true)
132
+ when :transient
133
+ attributes[:transient] = core_data_boolean(value)
134
+ when :indexed
135
+ attributes[:indexed] = core_data_boolean(value)
136
+ when :spotlight
137
+ attributes[:spotlightIndexingEnabled] = core_data_boolean(value)
138
+ when :truth_file
139
+ attributes[:storedInTruthFile] = core_data_boolean(value)
140
+ when :min
141
+ if type == :date
142
+ attributes[:minDateTimeInterval] = core_data_date(value)
143
+ else
144
+ attributes[:minValueString] = value
145
+ end
146
+ when :max
147
+ if type == :date
148
+ attributes[:maxDateTimeInterval] = core_data_date(value)
149
+ else
150
+ attributes[:maxValueString] = value
151
+ end
152
+ when :default
153
+ if type == :date
154
+ attributes[:defaultDateTimeInterval] = core_data_date(value)
155
+ elsif type == :boolean
156
+ attributes[:defaultValueString] = core_data_boolean(value)
157
+ else
158
+ attributes[:defaultValueString] = value
159
+ end
160
+ when :regex
161
+ attributes[:regularExpressionString] = value
162
+ when :external_storage
163
+ attributes[:allowsExternalBinaryDataStorage] = core_data_boolean(value)
164
+ end
165
+ end
166
+ attributes
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end