moblues 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +44 -0
  7. data/Rakefile +4 -0
  8. data/bin/moblues +5 -0
  9. data/lib/extensions/hash.rb +9 -0
  10. data/lib/moblues/cli.rb +26 -0
  11. data/lib/moblues/data_model/attribute.rb +15 -0
  12. data/lib/moblues/data_model/entity.rb +17 -0
  13. data/lib/moblues/data_model/relationship.rb +17 -0
  14. data/lib/moblues/data_model.rb +3 -0
  15. data/lib/moblues/generator/base.rb +65 -0
  16. data/lib/moblues/generator/human.rb +25 -0
  17. data/lib/moblues/generator/human_header.h.erb +4 -0
  18. data/lib/moblues/generator/human_implementation.m.erb +4 -0
  19. data/lib/moblues/generator/machine.rb +36 -0
  20. data/lib/moblues/generator/machine_header.h.erb +43 -0
  21. data/lib/moblues/generator/machine_implementation.m.erb +7 -0
  22. data/lib/moblues/generator/model.rb +23 -0
  23. data/lib/moblues/generator.rb +4 -0
  24. data/lib/moblues/reader/attribute.rb +23 -0
  25. data/lib/moblues/reader/entity.rb +34 -0
  26. data/lib/moblues/reader/model.rb +31 -0
  27. data/lib/moblues/reader/relationship.rb +22 -0
  28. data/lib/moblues/reader/type.rb +58 -0
  29. data/lib/moblues/reader.rb +5 -0
  30. data/lib/moblues/utils/model_resolver.rb +19 -0
  31. data/lib/moblues/version.rb +3 -0
  32. data/lib/moblues.rb +4 -0
  33. data/moblues.gemspec +33 -0
  34. data/spec/features/moblues_spec.rb +36 -0
  35. data/spec/moblues/cli_spec.rb +50 -0
  36. data/spec/moblues/data_model/attribute_spec.rb +30 -0
  37. data/spec/moblues/data_model/entity_spec.rb +48 -0
  38. data/spec/moblues/data_model/relationship_spec.rb +46 -0
  39. data/spec/moblues/generator/human_spec.rb +74 -0
  40. data/spec/moblues/generator/machine_spec.rb +86 -0
  41. data/spec/moblues/generator/model_spec.rb +27 -0
  42. data/spec/moblues/reader/attribute_spec.rb +22 -0
  43. data/spec/moblues/reader/entity_spec.rb +57 -0
  44. data/spec/moblues/reader/model_spec.rb +66 -0
  45. data/spec/moblues/reader/relationship_spec.rb +22 -0
  46. data/spec/moblues/reader/type_spec.rb +79 -0
  47. data/spec/moblues/utils/model_resolver_spec.rb +28 -0
  48. data/spec/resources/Model.xcdatamodeld/.xccurrentversion +8 -0
  49. data/spec/resources/Model.xcdatamodeld/Model 2.xcdatamodel/contents +28 -0
  50. data/spec/resources/Model.xcdatamodeld/Model.xcdatamodel/contents +18 -0
  51. data/spec/resources/expected/Author.h +4 -0
  52. data/spec/resources/expected/Author.m +4 -0
  53. data/spec/resources/expected/_Author.h +36 -0
  54. data/spec/resources/expected/_Author.m +9 -0
  55. data/spec/resources/expected/_Book.h +19 -0
  56. data/spec/resources/expected/_Book.m +6 -0
  57. data/spec/resources/expected/_Person.h +9 -0
  58. data/spec/resources/expected/_Person.m +6 -0
  59. data/spec/resources/expected/_Team.h +18 -0
  60. data/spec/resources/expected/_Team.m +5 -0
  61. data/spec/resources/expected/human/Playable.h +4 -0
  62. data/spec/resources/expected/human/Playable.m +4 -0
  63. data/spec/resources/expected/human/Playlist.h +5 -0
  64. data/spec/resources/expected/human/Playlist.m +4 -0
  65. data/spec/resources/expected/human/Track.h +4 -0
  66. data/spec/resources/expected/human/Track.m +4 -0
  67. data/spec/resources/expected/human/User.h +4 -0
  68. data/spec/resources/expected/human/User.m +4 -0
  69. data/spec/resources/expected/machine/_Playable.h +9 -0
  70. data/spec/resources/expected/machine/_Playable.m +6 -0
  71. data/spec/resources/expected/machine/_Playlist.h +28 -0
  72. data/spec/resources/expected/machine/_Playlist.m +6 -0
  73. data/spec/resources/expected/machine/_Track.h +14 -0
  74. data/spec/resources/expected/machine/_Track.m +7 -0
  75. data/spec/resources/expected/machine/_User.h +27 -0
  76. data/spec/resources/expected/machine/_User.m +8 -0
  77. data/spec/resources/factories/data_model/attribute_factory.rb +12 -0
  78. data/spec/resources/factories/data_model/entity_factory.rb +13 -0
  79. data/spec/resources/factories/data_model/relationship_factory.rb +14 -0
  80. data/spec/resources/fixtures.rb +34 -0
  81. data/spec/resources/tmp/.gitkeep +0 -0
  82. data/spec/resources/tmp/_Team.h +18 -0
  83. data/spec/resources/tmp/_Team.m +5 -0
  84. data/spec/resources/tmp/human/Playlist.h +5 -0
  85. data/spec/resources/tmp/human/Playlist.m +4 -0
  86. data/spec/spec_helper.rb +15 -0
  87. metadata +285 -0
data/moblues.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'moblues/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'moblues'
8
+ spec.version = Moblues::VERSION
9
+ spec.authors = ['Vincent Garrigues']
10
+ spec.email = ['vincent.garrigues@gmail.com']
11
+ spec.summary = 'Generates Objective-C files for entities defined in a Core Data model'
12
+ spec.description = %q{
13
+ Generates files for entities defined in a Core Data model. Inspired by mogenerator.
14
+
15
+ For each entity in the Core Data model, moblues will create two files: a machine file and a human file. The machine file will be overwritten each time and should not contain any modifications. Moblues will only create the human file if it doesn't exist.
16
+ }
17
+ spec.homepage = 'http://garriguv.io'
18
+ spec.license = 'MIT'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0")
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_runtime_dependency 'thor', '~> 0.19'
26
+ spec.add_runtime_dependency 'plist', '~> 3.1'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.7'
29
+ spec.add_development_dependency 'rake', '~> 10.3'
30
+ spec.add_development_dependency 'rspec', '~> 3.1'
31
+ spec.add_development_dependency 'factory_girl', '~> 4.5'
32
+ spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.4.1'
33
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'moblues/cli'
3
+
4
+ describe 'moblues' do
5
+ subject { Moblues::CLI }
6
+ let(:perform) { subject.start([command] + options) }
7
+
8
+ describe 'generate' do
9
+ let(:command) { 'generate' }
10
+ let(:options) { %w{--model=spec/resources/Model.xcdatamodeld --human=spec/resources/tmp/human --machine=spec/resources/tmp/machine} }
11
+
12
+ after do
13
+ Fixtures.delete_tmp_files(machine_files + human_files.select { |f| !f.include?('Playlist') } )
14
+ end
15
+
16
+ it 'generates human and machine files' do
17
+ perform
18
+
19
+ (machine_files + human_files).each do |file|
20
+ expect(Fixtures.read_file(Fixtures.generated_dir, file)).to eq(Fixtures.read_file(Fixtures.expected_dir, file))
21
+ end
22
+ end
23
+ end
24
+
25
+ def machine_files
26
+ entities.map{|f| "machine/_#{f}" }.map{ |f| %W(#{f}.h #{f}.m) }.flatten
27
+ end
28
+
29
+ def human_files
30
+ entities.map{|f| "human/#{f}" }.map{ |f| %W(#{f}.h #{f}.m) }.flatten
31
+ end
32
+
33
+ def entities
34
+ %w{User Playable Track Playlist}
35
+ end
36
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'moblues/cli'
3
+
4
+ describe Moblues::CLI do
5
+ subject { described_class }
6
+ let(:perform) { subject.start([command] + options, debug: true) }
7
+
8
+ context 'when command is invalid' do
9
+ let(:command) { 'invalid' }
10
+ let(:options) { [] }
11
+
12
+ it 'fails' do
13
+ expect { perform }.to raise_error
14
+ end
15
+ end
16
+
17
+ context 'generate' do
18
+ let(:command) { 'generate' }
19
+
20
+ context 'when options missing' do
21
+ let(:options) { [] }
22
+
23
+ it 'fails' do
24
+ expect { perform }.to raise_error
25
+ end
26
+ end
27
+
28
+ context 'when options given' do
29
+ let(:options) { %w{--model=model/path --human=human/path --machine=machine/path} }
30
+
31
+ let(:model_reader) { double(Moblues::Reader::Model) }
32
+ let(:model_generator) { double(Moblues::Generator::Model) }
33
+ let(:entities) { [build(:entity)] }
34
+
35
+ before do
36
+ allow(Moblues::Reader::Model).to receive(:new) { model_reader }
37
+ allow(Moblues::Generator::Model).to receive(:new) { model_generator }
38
+ end
39
+
40
+ it 'created the directories reads the model and generates the files' do
41
+ expect(Dir).to receive(:mkdir).with('human/path').once
42
+ expect(Dir).to receive(:mkdir).with('machine/path').once
43
+ expect(model_reader).to receive(:model).with('model/path').once { entities }
44
+ expect(model_generator).to receive(:generate).with(entities).once
45
+
46
+ perform
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require 'moblues/data_model/attribute'
3
+
4
+ module Moblues
5
+ module DataModel
6
+ describe Attribute do
7
+ describe '#initialize' do
8
+ context 'when name and type provided' do
9
+ subject { described_class.new(name: 'attribute', type: 'type') }
10
+
11
+ it 'returns an Attribute object' do
12
+ expect(subject).to eq(Attribute.new(name: 'attribute', type: 'type'))
13
+ end
14
+ end
15
+
16
+ context 'when name missing' do
17
+ it 'raises an assertion' do
18
+ expect { Attribute.new(type: 'type') }.to raise_exception(KeyError)
19
+ end
20
+ end
21
+
22
+ context 'when type missing' do
23
+ it 'raises an assertion' do
24
+ expect { Attribute.new(name: 'attribute') }.to raise_exception(KeyError)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'moblues/data_model/entity'
3
+
4
+ module Moblues
5
+ module DataModel
6
+ describe Entity do
7
+ describe '#initialize' do
8
+ context 'when name, parent_entity, attributes and relationship provided' do
9
+ subject { described_class.new(name: 'AudioBook', parent_entity: 'Book', attributes: ['title'], relationships: ['author']) }
10
+
11
+ it 'returns an Entity object' do
12
+ expect(subject).to eq(Entity.new(name: 'AudioBook', parent_entity: 'Book', attributes: ['title'], relationships: ['author']))
13
+ end
14
+ end
15
+
16
+ context 'when name missing' do
17
+ it 'raises an exception' do
18
+ expect { Entity.new(parent_entity: 'Book', attributes: ['title'], relationships: ['author']) }.to raise_exception(KeyError)
19
+ end
20
+ end
21
+
22
+ context 'when parent_entity missing' do
23
+ subject { described_class.new(name: 'AudioBook', attributes: ['title'], relationships: ['author']) }
24
+
25
+ it 'returns an Entity with NSManagedObject parent_entity' do
26
+ expect(subject).to eq(Entity.new(name: 'AudioBook', parent_entity: 'NSManagedObject', attributes: ['title'], relationships: ['author']))
27
+ end
28
+ end
29
+
30
+ context 'when attributes missing' do
31
+ subject { described_class.new(name: 'AudioBook', parent_entity: 'Book', relationships: ['author']) }
32
+
33
+ it 'returns an Entity with empty attributes' do
34
+ expect(subject).to eq(Entity.new(name: 'AudioBook', parent_entity: 'Book', attributes: [], relationships: ['author']))
35
+ end
36
+ end
37
+
38
+ context 'when relationships missing' do
39
+ subject { described_class.new(name: 'AudioBook', parent_entity: 'Book', attributes: ['title']) }
40
+
41
+ it 'returns an Entity with empty relationships' do
42
+ expect(subject).to eq(Entity.new(name: 'AudioBook', parent_entity: 'Book', attributes: ['title'], relationships: []))
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'moblues/data_model/relationship'
3
+
4
+ module Moblues
5
+ module DataModel
6
+ describe Relationship do
7
+ describe '#initialize' do
8
+ context 'when name, destination_entity, to_many and ordered provided' do
9
+ subject { described_class.new(name: 'relationship', destination_entity: 'entity', to_many: true, ordered: true) }
10
+
11
+ it 'returns a Relationship object' do
12
+ expect(subject).to eq(Relationship.new(name: 'relationship', destination_entity: 'entity', to_many: true, ordered: true))
13
+ end
14
+ end
15
+
16
+ context 'when name missing' do
17
+ it 'raises an assertion' do
18
+ expect { Relationship.new(destination_entity: 'entity', to_many: true, ordered: true) }.to raise_exception(KeyError)
19
+ end
20
+ end
21
+
22
+ context 'when destination_entity missing' do
23
+ it 'raises an assertion' do
24
+ expect { Relationship.new(name: 'relationship', to_many: true, ordered: true) }.to raise_exception(KeyError)
25
+ end
26
+ end
27
+
28
+ context 'when to_many missing' do
29
+ subject { described_class.new(name: 'relationship', destination_entity: 'entity', ordered: true) }
30
+
31
+ it 'returns a Relationship with to_many false' do
32
+ expect(subject).to eq(Relationship.new(name: 'relationship', destination_entity: 'entity', to_many: false, ordered: true))
33
+ end
34
+ end
35
+
36
+ context 'when ordered missing' do
37
+ subject { described_class.new(name: 'relationship', destination_entity: 'entity', to_many: true) }
38
+
39
+ it 'returns a Relationship with ordered false' do
40
+ expect(subject).to eq(Relationship.new(name: 'relationship', destination_entity: 'entity', to_many: true, ordered: false))
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+ require 'moblues/generator/human'
3
+ require 'moblues/data_model'
4
+
5
+ describe Moblues::Generator::Human do
6
+ subject { described_class.new(output_dir: Fixtures.generated_dir) }
7
+
8
+ after do
9
+ Fixtures.delete_tmp_files(%w{Author.h Author.m})
10
+ end
11
+
12
+ describe '#generate' do
13
+ it 'generates a human header' do
14
+ subject.generate(entity)
15
+
16
+ expect(Fixtures.generated_file_content('Author.h')).to eq(Fixtures.expected_content('Author.h'))
17
+ end
18
+
19
+ it 'generates a human implementation' do
20
+ subject.generate(entity)
21
+
22
+ expect(Fixtures.generated_file_content('Author.m')).to eq(Fixtures.expected_content('Author.m'))
23
+ end
24
+
25
+ context 'if the header already exists' do
26
+ before do
27
+ File.open(File.join(Fixtures.generated_dir, 'Author.h'), 'w+') do |f|
28
+ f.write('do nothing')
29
+ end
30
+ end
31
+
32
+ it 'does not overwrite the header file' do
33
+ subject.generate(entity)
34
+
35
+ expect(Fixtures.generated_file_content('Author.h')).to eq('do nothing')
36
+ end
37
+ end
38
+
39
+ context 'if the implementation already exists' do
40
+ before do
41
+ File.open(File.join(Fixtures.generated_dir, 'Author.m'), 'w+') do |f|
42
+ f.write('do nothing')
43
+ end
44
+ end
45
+
46
+ it 'does not overwrite the implementation file' do
47
+ subject.generate(entity)
48
+
49
+ expect(Fixtures.generated_file_content('Author.m')).to eq('do nothing')
50
+ end
51
+ end
52
+ end
53
+
54
+ def entity
55
+ Moblues::DataModel::Entity.new(name: 'Author',
56
+ attributes: attributes,
57
+ relationships: relationships)
58
+ end
59
+
60
+ def attributes
61
+ [
62
+ Moblues::DataModel::Attribute.new(name: 'dob', type: :date),
63
+ Moblues::DataModel::Attribute.new(name: 'name', type: :string)
64
+ ]
65
+ end
66
+
67
+ def relationships
68
+ [
69
+ Moblues::DataModel::Relationship.new(name: 'books', destination_entity: 'Book', to_many: true, ordered: true),
70
+ Moblues::DataModel::Relationship.new(name: 'essays', destination_entity: 'Book', to_many: true),
71
+ Moblues::DataModel::Relationship.new(name: 'publisher', destination_entity: 'Publisher')
72
+ ]
73
+ end
74
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+ require 'moblues/generator/machine'
3
+ require 'moblues/data_model'
4
+
5
+ describe Moblues::Generator::Machine do
6
+ subject { described_class.new(output_dir: Fixtures.generated_dir) }
7
+
8
+ after do
9
+ Fixtures.delete_tmp_files(%w{_Author.h _Author.m _Person.h _Person.m _Book.h _Book.m})
10
+ end
11
+
12
+ describe '#generate' do
13
+ shared_examples_for 'machine_generator' do |name|
14
+ it 'generates a header' do
15
+ subject.generate(entity)
16
+
17
+ expect(Fixtures.generated_file_content(header(name))).to eq(Fixtures.expected_content(header(name)))
18
+ end
19
+
20
+ it 'generates an implementation' do
21
+ subject.generate(entity)
22
+
23
+ expect(Fixtures.generated_file_content(implementation(name))).to eq(Fixtures.expected_content(implementation(name)))
24
+ end
25
+ end
26
+
27
+ context 'with a complex entity' do
28
+ let(:entity) { Moblues::DataModel::Entity.new(name: 'Author',
29
+ parent_entity: 'Person',
30
+ attributes: attributes,
31
+ relationships: relationships) }
32
+ let(:attributes) {[
33
+ Moblues::DataModel::Attribute.new(name: 'dob', type: :date),
34
+ Moblues::DataModel::Attribute.new(name: 'name', type: :string)
35
+ ]}
36
+ let(:relationships) {[
37
+ Moblues::DataModel::Relationship.new(name: 'books', destination_entity: 'Book', to_many: true, ordered: true),
38
+ Moblues::DataModel::Relationship.new(name: 'essays', destination_entity: 'Book', to_many: true),
39
+ Moblues::DataModel::Relationship.new(name: 'publisher', destination_entity: 'Publisher')
40
+ ]}
41
+
42
+ it_behaves_like 'machine_generator', 'Author'
43
+ end
44
+
45
+ context 'with an entity without relationships' do
46
+ let(:entity) { Moblues::DataModel::Entity.new(name: 'Person',
47
+ attributes: [Moblues::DataModel::Attribute.new(name: 'name', type: :string),
48
+ Moblues::DataModel::Attribute.new(name: 'dob', type: :date)]) }
49
+
50
+ it_behaves_like 'machine_generator', 'Person'
51
+ end
52
+
53
+ context 'with an entity with attributes, relationships but NO forward declaration' do
54
+ let(:entity) { Moblues::DataModel::Entity.new(name: 'Book',
55
+ attributes: attributes,
56
+ relationships: relationships) }
57
+ let(:attributes) {[
58
+ Moblues::DataModel::Attribute.new(name: 'name', type: :string)
59
+ ]}
60
+ let(:relationships) {[
61
+ Moblues::DataModel::Relationship.new(name: 'editions', destination_entity: 'Edition', to_many: true)
62
+ ]}
63
+
64
+ it_behaves_like 'machine_generator', 'Book'
65
+ end
66
+
67
+ context 'with an entity that has a to-many relationship an a variable name that contains capitals' do
68
+ let(:entity) { Moblues::DataModel::Entity.new(name: 'Team',
69
+ attributes: [],
70
+ relationships: relationships) }
71
+ let(:relationships) {[
72
+ Moblues::DataModel::Relationship.new(name: 'teamMembers', destination_entity: 'Person', to_many: true)
73
+ ]}
74
+
75
+ it_behaves_like 'machine_generator', 'Team'
76
+ end
77
+ end
78
+
79
+ def header(name)
80
+ "_#{name}.h"
81
+ end
82
+
83
+ def implementation(name)
84
+ "_#{name}.h"
85
+ end
86
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Moblues::Generator::Model do
4
+ describe '#generate' do
5
+ subject { described_class.new(human: human, machine: machine, human_dir: human_dir, machine_dir: machine_dir) }
6
+
7
+ let(:entities) { [build(:entity)] }
8
+ let(:entity) { entities.first }
9
+ let(:human_dir) { 'human/dir'}
10
+ let(:machine_dir) { 'machine/dir'}
11
+
12
+ let(:human) { double(Moblues::Generator::Human) }
13
+ let(:machine) { double(Moblues::Generator::Machine) }
14
+
15
+ before do
16
+ allow(Moblues::Generator::Human).to receive(:new).with(output_dir: human_dir) { human }
17
+ allow(Moblues::Generator::Machine).to receive(:new).with(output_dir: machine_dir) { machine }
18
+ end
19
+
20
+ it 'generates the human and machine files' do
21
+ expect(human).to receive(:generate).with(entity).once
22
+ expect(machine).to receive(:generate).with(entity).once
23
+
24
+ subject.generate(entities)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+ require 'moblues/reader/attribute'
3
+
4
+ describe Moblues::Reader::Attribute do
5
+ describe '#attribute' do
6
+ context 'when XML is valid' do
7
+ let(:xml) { REXML::Document.new('<attribute name="testName" attributeType="String"/>').elements.first }
8
+
9
+ it 'returns an Attribute object' do
10
+ expect(subject.attribute(xml)).to eq(Moblues::DataModel::Attribute.new(name: 'testName', type: :string))
11
+ end
12
+ end
13
+
14
+ context 'when XML is invalid' do
15
+ let(:xml) { REXML::Document.new('<notAnAttribute/>').elements.first }
16
+
17
+ it 'raises an assertion' do
18
+ expect { subject.attribute(xml) }.to raise_exception
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'moblues/reader/entity'
3
+
4
+ describe Moblues::Reader::Entity do
5
+ describe '#entity' do
6
+ context 'when XML is valid' do
7
+ subject { described_class.new(attribute_reader: attribute_reader, relationship_reader: relationship_reader) }
8
+
9
+ let(:attribute_reader) { double(Moblues::Reader::Attribute) }
10
+ let(:relationship_reader) { double(Moblues::Reader::Relationship) }
11
+ let(:xml) {
12
+ xml_str = <<EOF
13
+ <entity name="Author" parentEntity="Person" syncable="YES">
14
+ <attribute name="dob" attributeType="Date" syncable="YES"/>
15
+ <attribute name="name" attributeType="String" syncable="YES"/>
16
+ <relationship name="books" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Book" inverseName="author" inverseEntity="Book" syncable="YES"/>
17
+ </entity>
18
+ EOF
19
+ REXML::Document.new(xml_str).elements.first
20
+ }
21
+
22
+ before(:each) do
23
+ allow(attribute_reader).to receive(:attribute) { build(:attribute) }
24
+ allow(relationship_reader).to receive(:relationship) { build(:relationship) }
25
+ end
26
+
27
+ it 'maps attributes' do
28
+ expect(attribute_reader).to receive(:attribute).twice
29
+
30
+ subject.entity(xml)
31
+ end
32
+
33
+ it 'maps relationships' do
34
+ expect(relationship_reader).to receive(:relationship).once
35
+
36
+ subject.entity(xml)
37
+ end
38
+
39
+ it 'returns an Entity' do
40
+ entity = Moblues::DataModel::Entity.new(name: 'Author',
41
+ parent_entity: 'Person',
42
+ attributes: [build(:attribute), build(:attribute)],
43
+ relationships: [build(:relationship)])
44
+
45
+ expect(subject.entity(xml)).to eq(entity)
46
+ end
47
+ end
48
+
49
+ context 'when XML is invalid' do
50
+ let(:xml) { REXML::Document.new('<notAnEntity/>').elements.first }
51
+
52
+ it 'raises an exception' do
53
+ expect { subject.entity(xml) }.to raise_exception
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'moblues/reader/model'
3
+
4
+ describe Moblues::Reader::Model do
5
+ let(:model) { build(:model) }
6
+ let(:model_resolver) { double(Moblues::Utils::ModelResolver) }
7
+ let(:entity_reader) { double(Moblues::Reader::Entity) }
8
+ let(:file_str) {
9
+ <<EOF
10
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
11
+ <model userDefinedModelVersionIdentifier="2.0" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6252" systemVersion="14A389" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">
12
+ <entity name="Author" syncable="YES">
13
+ <attribute name="dob" attributeType="Date" syncable="YES"/>
14
+ <attribute name="name" attributeType="String" syncable="YES"/>
15
+ <relationship name="books" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Book" inverseName="author" inverseEntity="Book" syncable="YES"/>
16
+ </entity>
17
+ <entity name="Book" syncable="YES">
18
+ <attribute name="numberOfPages" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
19
+ <attribute name="title" attributeType="String" syncable="YES"/>
20
+ <relationship name="author" maxCount="1" deletionRule="Nullify" destinationEntity="Author" inverseName="books" inverseEntity="Author" syncable="YES"/>
21
+ <relationship name="publisher" maxCount="1" deletionRule="Nullify" destinationEntity="Publisher" inverseName="books" inverseEntity="Publisher" syncable="YES"/>
22
+ </entity>
23
+ <entity name="Publisher" syncable="YES">
24
+ <attribute name="name" attributeType="String" syncable="YES"/>
25
+ <attribute name="website" optional="YES" attributeType="String" syncable="YES"/>
26
+ <relationship name="books" maxCount="1" deletionRule="Nullify" destinationEntity="Book" inverseName="publisher" inverseEntity="Book" syncable="YES"/>
27
+ </entity>
28
+ <elements>
29
+ <element name="Author" positionX="-281" positionY="-39" width="128" height="88"/>
30
+ <element name="Book" positionX="-54" positionY="-9" width="128" height="103"/>
31
+ <element name="Publisher" positionX="153" positionY="6" width="128" height="88"/>
32
+ </elements>
33
+ </model>
34
+ EOF
35
+ }
36
+
37
+ subject { described_class.new(resolver: model_resolver, reader: entity_reader) }
38
+
39
+ before do
40
+ allow(model_resolver).to receive(:resolve_model)
41
+ allow(entity_reader).to receive(:entity) { build(:entity) }
42
+ allow(File).to receive(:read) { file_str }
43
+ end
44
+
45
+ describe '#parse_model' do
46
+ it 'resolves the model contents' do
47
+ expect(model_resolver).to receive(:resolve_model).with('path')
48
+
49
+ subject.model('path')
50
+ end
51
+
52
+ it 'maps the model' do
53
+ expect(entity_reader).to receive(:entity).exactly(3).times
54
+
55
+ subject.model('path')
56
+ end
57
+
58
+ it 'returns an array of entities' do
59
+ expect(subject.model('path')).to eq([build(:entity), build(:entity), build(:entity)])
60
+ end
61
+
62
+ it 'raises an assertion if path is nil' do
63
+ expect { subject.model(nil) }.to raise_exception
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+ require 'moblues/reader/relationship'
3
+
4
+ describe Moblues::Reader::Relationship do
5
+ describe '#relationship' do
6
+ context 'when XML is valid' do
7
+ let(:xml) { REXML::Document.new('<relationship name="testName" toMany="YES" destinationEntity="TestEntity" ordered="YES"/>').elements.first }
8
+
9
+ it 'returns a Relationship' do
10
+ expect(subject.relationship(xml)).to eq(Moblues::DataModel::Relationship.new(name: 'testName', destination_entity: 'TestEntity', to_many: true, ordered: true))
11
+ end
12
+ end
13
+
14
+ context 'when XML is invalid' do
15
+ let(:xml) { REXML::Document.new('<notARelationship/>').elements.first }
16
+
17
+ it 'raises an exception' do
18
+ expect { subject.relationship(xml) }.to raise_exception
19
+ end
20
+ end
21
+ end
22
+ end