motion_migrate 0.1.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.
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