datamappify 0.52.0 → 0.53.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cd079889034877268b854c9c1402e747eb84348a
4
- data.tar.gz: ea1988a5cea43748d9d55658e999b9c1a95a9de6
3
+ metadata.gz: bad8103a3c5c165af76967bc3b93b2c1a79c303d
4
+ data.tar.gz: a84ea4a4f05701f9bdbbb4ef68baba89f2887dc9
5
5
  SHA512:
6
- metadata.gz: 18fde8f5ddccee08904440fdd706f0563130a78566a8860bc3a67017b35320444655db5226e6019d73e29e069750a80ddc267dd7e0bcc63d64a84daf05d263c8
7
- data.tar.gz: 91fa0a11fd1af61e398b0b22e6dcad429c4123653dafabcb9c70bc39681ce0a8a9b1e61944996f688eac8221b771344a02679f9b137d3cc278a57a4b6b9114f6
6
+ metadata.gz: 49c79d48411f0e71f550aeba3d0022cae4e8bf1a61aae621493164da4f64942034f348e34a0a1fe4a66221bc86cf6a744c22cf232c8722ca2580336f758b4463
7
+ data.tar.gz: e6e75cdce7912e86ce5e023f3e44fee19e6c890b23c77159b883db53a53ee92b295e456aca40fef871ca997252c49139dfde9ac6190abf317707e55e501ddccc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## master
2
2
 
3
+ ## 0.53.0 [2013-06-27]
4
+
5
+ - Added support for mapping to namespaced data models.
6
+
3
7
  ## 0.52.0 [2013-06-26]
4
8
 
5
9
  - Added support for reverse mapping attributes.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Datamappify [![Gem Version](https://badge.fury.io/rb/datamappify.png)](http://badge.fury.io/rb/datamappify) [![Build Status](https://api.travis-ci.org/fredwu/datamappify.png?branch=master)](http://travis-ci.org/fredwu/datamappify) [![Coverage Status](https://coveralls.io/repos/fredwu/datamappify/badge.png)](https://coveralls.io/r/fredwu/datamappify) [![Code Climate](https://codeclimate.com/github/fredwu/datamappify.png)](https://codeclimate.com/github/fredwu/datamappify)
2
2
 
3
- #### Compose, decouple and manage domain logic and data persistence separately. Works great with forms!
3
+ #### Compose, decouple and manage domain logic and data persistence separately. Works particularly great for composing form objects!
4
4
 
5
5
  ## Overview
6
6
 
@@ -27,7 +27,7 @@ Datamappify consists of three components:
27
27
 
28
28
  Below is a high level and somewhat simplified overview of Datamappify's architecture.
29
29
 
30
- ![](http://i.imgur.com/byt6P3f.png)
30
+ ![](http://i.imgur.com/I9GpLds.png)
31
31
 
32
32
  Note: Datamappify is NOT affiliated with the [Datamapper](https://github.com/datamapper/) project.
33
33
 
data/datamappify.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Datamappify::VERSION
9
9
  spec.authors = ["Fred Wu"]
10
10
  spec.email = ["ifredwu@gmail.com"]
11
- spec.description = %q{Compose, decouple and manage domain logic and data persistence separately. Works great with forms!}
11
+ spec.description = %q{Compose, decouple and manage domain logic and data persistence separately. Works particularly great for composing form objects!}
12
12
  spec.summary = spec.description
13
13
  spec.homepage = "https://github.com/fredwu/datamappify"
14
14
  spec.license = "MIT"
@@ -56,7 +56,7 @@ module Datamappify
56
56
 
57
57
  # @example
58
58
  #
59
- # UserComment
59
+ # Namespaced::UserComment
60
60
  #
61
61
  # @return [Class]
62
62
  def source_class
@@ -69,7 +69,7 @@ module Datamappify
69
69
  #
70
70
  # @return [String]
71
71
  def source_name
72
- @source_name ||= source_class_name.underscore
72
+ @source_name ||= source_class_name.demodulize.underscore
73
73
  end
74
74
 
75
75
  # @example
@@ -141,8 +141,9 @@ module Datamappify
141
141
  provider_name != primary_provider_name
142
142
  end
143
143
 
144
+ # @return [Boolean]
144
145
  def reverse_mapped?
145
- @options[:via]
146
+ !!@options[:via]
146
147
  end
147
148
 
148
149
  private
@@ -150,7 +151,7 @@ module Datamappify
150
151
  # @return [Array<String>]
151
152
  # an array with provider name, source class name and source attribute name
152
153
  def parse_source(source)
153
- provider_name, source_class_and_attribute = source.split('::')
154
+ provider_name, source_class_and_attribute = source.split('::', 2)
154
155
 
155
156
  [provider_name, *source_class_and_attribute.split('#')]
156
157
  end
@@ -9,9 +9,15 @@ module Datamappify
9
9
  #
10
10
  # @return [ActiveRecord::Base]
11
11
  def build_record_class(source_class_name)
12
- Datamappify::Data::Record::ActiveRecord.const_set(
13
- source_class_name, Class.new(::ActiveRecord::Base)
14
- )
12
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
13
+ module Datamappify::Data::Record::ActiveRecord
14
+ class #{source_class_name} < ::ActiveRecord::Base
15
+ self.table_name = '#{source_class_name.pluralize.gsub('::', '_').underscore}'
16
+ end
17
+ end
18
+ CODE
19
+
20
+ Datamappify::Data::Record::ActiveRecord.const_get(source_class_name)
15
21
  end
16
22
 
17
23
  # @return [void]
@@ -35,8 +35,18 @@ module Datamappify
35
35
  # @return [Class]
36
36
  # the data record class
37
37
  def find_or_build_record_class(source_class_name)
38
- if records_namespace.const_defined?(source_class_name, false)
39
- records_namespace.const_get(source_class_name)
38
+ namespace = build_or_return_namespace(source_class_name)
39
+ record_class = source_class_name.safe_constantize
40
+
41
+ # check for existing record class
42
+ if record_class && !entity_class?(source_class_name)
43
+ record_class
44
+
45
+ # check for existing record class in the Datamappify::Data::Record::Provider namespace
46
+ elsif namespace.const_defined?(source_class_name.demodulize, false)
47
+ namespace.const_get(source_class_name.demodulize)
48
+
49
+ # no existing record class is found, let's build it
40
50
  else
41
51
  build_record_class(source_class_name)
42
52
  end
@@ -44,6 +54,26 @@ module Datamappify
44
54
 
45
55
  private
46
56
 
57
+ # @return [Boolean]
58
+ def entity_class?(source_class_name)
59
+ source_class_name.constantize.ancestors.include?(Datamappify::Entity)
60
+ end
61
+
62
+ # Builds or returns the namespace for the source class
63
+ #
64
+ # @param source_class_name [String]
65
+ #
66
+ # @return [Module]
67
+ def build_or_return_namespace(source_class_name)
68
+ source_class_name.deconstantize.split('::').inject(records_namespace) do |namespaces, namespace|
69
+ if namespaces.const_defined?(namespace, false)
70
+ namespaces.const_get(namespace)
71
+ else
72
+ namespaces.const_set(namespace, Module.new)
73
+ end
74
+ end
75
+ end
76
+
47
77
  # The namespace for the data records, e.g. +Datamappify::Data::Record::ActiveRecord+
48
78
  #
49
79
  # @return [Module]
@@ -9,11 +9,15 @@ module Datamappify
9
9
  #
10
10
  # @return [Sequel::Model]
11
11
  def build_record_class(source_class_name)
12
- Record::Sequel.const_set(
13
- source_class_name, Class.new(::Sequel::Model(source_class_name.pluralize.underscore.to_sym))
14
- ).tap do |klass|
15
- klass.raise_on_save_failure = true
16
- end
12
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
13
+ module Datamappify::Data::Record::Sequel
14
+ class #{source_class_name} < ::Sequel::Model(:#{source_class_name.pluralize.gsub('::', '_').underscore})
15
+ raise_on_save_failure = true
16
+ end
17
+ end
18
+ CODE
19
+
20
+ Datamappify::Data::Record::Sequel.const_get(source_class_name)
17
21
  end
18
22
 
19
23
  # @param attribute (see Record#build_association)
@@ -1,3 +1,3 @@
1
1
  module Datamappify
2
- VERSION = '0.52.0'
2
+ VERSION = '0.53.0'
3
3
  end
@@ -9,34 +9,22 @@ describe Datamappify::Entity do
9
9
  :gfx => 'Voodoo',
10
10
  :vendor => 'Compaq',
11
11
  :software_os => 'OS X',
12
- :software_osx_id => 1,
13
- :software_windows_id => 2,
14
- :software_linux_id => 3,
15
- :software_vendor => 'Lotus',
16
- :game_os => 'OS X',
17
- :game_osx_id => 1,
18
- :game_windows_id => 2,
19
- :game_linux_id => 3,
20
- :game_vendor => 'Lotus'
12
+ :software_vendor => 'Apple',
13
+ :game_os => 'Orbit OS',
14
+ :game_vendor => 'SONY'
21
15
  })
22
16
  end
23
17
 
24
- its(:brand) { should == 'Fruit' }
25
- its(:cpu) { should == '286' }
26
- its(:ram) { should == 4242 }
27
- its(:hdd) { should == 65536 }
28
- its(:gfx) { should == 'Voodoo' }
29
- its(:vendor) { should == 'Compaq' }
30
- its(:software_os) { should == 'OS X' }
31
- its(:software_osx_id) { should == 1 }
32
- its(:software_windows_id) { should == 2 }
33
- its(:software_linux_id) { should == 3 }
34
- its(:software_vendor) { should == 'Lotus' }
35
- its(:game_os) { should == 'OS X' }
36
- its(:game_osx_id) { should == 1 }
37
- its(:game_windows_id) { should == 2 }
38
- its(:game_linux_id) { should == 3 }
39
- its(:game_vendor) { should == 'Lotus' }
18
+ its(:brand) { should == 'Fruit' }
19
+ its(:cpu) { should == '286' }
20
+ its(:ram) { should == 4242 }
21
+ its(:hdd) { should == 65536 }
22
+ its(:gfx) { should == 'Voodoo' }
23
+ its(:vendor) { should == 'Compaq' }
24
+ its(:software_os) { should == 'OS X' }
25
+ its(:software_vendor) { should == 'Apple' }
26
+ its(:game_os) { should == 'Orbit OS' }
27
+ its(:game_vendor) { should == 'SONY' }
40
28
 
41
29
  describe "validation" do
42
30
  context "valid" do
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "namespaced mapping" do |data_provider|
4
+ let!(:computer_repository) { "ComputerRepository#{data_provider}".constantize }
5
+ let(:new_computer) {
6
+ Computer.new({
7
+ :brand => 'Fruit',
8
+ :cpu => '286',
9
+ :ram => 4242,
10
+ :gfx => 'Voodoo',
11
+ :vendor => 'Compaq',
12
+ :software_os => 'OS X',
13
+ :software_vendor => 'Apple',
14
+ :game_os => 'Orbit OS',
15
+ :game_vendor => 'SONY'
16
+ })
17
+ }
18
+
19
+ context "#{data_provider}" do
20
+ describe "entity" do
21
+ let!(:computer) { computer_repository.save!(new_computer) }
22
+
23
+ subject { computer_repository.find(computer.id) }
24
+
25
+ its(:brand) { should == 'Fruit' }
26
+ its(:cpu) { should == '286' }
27
+ its(:ram) { should == 4242 }
28
+ its(:hdd) { should == 65536 }
29
+ its(:gfx) { should == 'Voodoo' }
30
+ its(:vendor) { should == 'Compaq' }
31
+ its(:software_os) { should == 'OS X' }
32
+ its(:software_vendor) { should == 'Apple' }
33
+ its(:game_os) { should == 'Orbit OS' }
34
+ its(:game_vendor) { should == 'SONY' }
35
+ end
36
+ end
37
+ end
38
+
39
+ describe Datamappify::Repository do
40
+ DATA_PROVIDERS.each do |data_provider|
41
+ it_behaves_like "namespaced mapping", data_provider
42
+ end
43
+ end
@@ -1,14 +1,14 @@
1
- require_relative 'computer_hardware'
2
- require_relative 'computer_software'
1
+ require_relative 'computer_component/hardware'
2
+ require_relative 'computer_component/software'
3
3
 
4
4
  class Computer
5
5
  include Datamappify::Entity
6
6
 
7
7
  attribute :brand, String
8
8
 
9
- attributes_from ComputerHardware
10
- attributes_from ComputerSoftware, :prefix_with => :software
11
- attributes_from ComputerSoftware, :prefix_with => :game
9
+ attributes_from ComputerComponent::Hardware
10
+ attributes_from ComputerComponent::Software, :prefix_with => :software
11
+ attributes_from ComputerComponent::Software, :prefix_with => :game
12
12
 
13
13
  validates :brand, :presence => true
14
14
  end
@@ -0,0 +1,14 @@
1
+ module ComputerComponent
2
+ class Hardware
3
+ include Datamappify::Entity
4
+
5
+ attribute :cpu, String
6
+ attribute :ram, Integer, :default => 8192
7
+ attribute :hdd, Integer, :default => 65536
8
+ attribute :gfx, String
9
+ attribute :vendor, String
10
+
11
+ validates :ram, :hdd, :numericality => { :greater_than_or_equal_to => 4096 }
12
+ validates :hdd, :numericality => { :less_than_or_equal_to => 65536 }
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ module ComputerComponent
2
+ class Software
3
+ include Datamappify::Entity
4
+
5
+ attribute :os, String
6
+ attribute :vendor, String
7
+
8
+ validates_presence_of :os
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ class ComputerSoftware < ActiveRecord::Base
2
+ self.table_name = 'computer_component_softwares'
3
+ end
4
+
5
+ class ComputerRepositoryActiveRecord
6
+ include Datamappify::Repository
7
+
8
+ for_entity Computer
9
+ default_provider :ActiveRecord
10
+
11
+ map_attribute :cpu, 'ActiveRecord::ComputerComponent::Hardware#cpu'
12
+ map_attribute :ram, 'ActiveRecord::ComputerComponent::Hardware#ram'
13
+ map_attribute :hdd, 'ActiveRecord::ComputerComponent::Hardware#hdd'
14
+ map_attribute :gfx, 'ActiveRecord::ComputerComponent::Hardware#gfx'
15
+ map_attribute :vendor, 'ActiveRecord::ComputerComponent::Hardware#vendor'
16
+ map_attribute :software_os, 'ActiveRecord::ComputerSoftware#os'
17
+ map_attribute :software_vendor, 'ActiveRecord::ComputerSoftware#vendor'
18
+ map_attribute :game_os, 'ActiveRecord::ComputerSoftware#os', :via => :game_id
19
+ map_attribute :game_vendor, 'ActiveRecord::ComputerSoftware#vendor', :via => :game_id
20
+ end
@@ -0,0 +1,16 @@
1
+ class ComputerRepositorySequel
2
+ include Datamappify::Repository
3
+
4
+ for_entity Computer
5
+ default_provider :Sequel
6
+
7
+ map_attribute :cpu, 'Sequel::ComputerComponent::Hardware#cpu'
8
+ map_attribute :ram, 'Sequel::ComputerComponent::Hardware#ram'
9
+ map_attribute :hdd, 'Sequel::ComputerComponent::Hardware#hdd'
10
+ map_attribute :gfx, 'Sequel::ComputerComponent::Hardware#gfx'
11
+ map_attribute :vendor, 'Sequel::ComputerComponent::Hardware#vendor'
12
+ map_attribute :software_os, 'Sequel::ComputerComponent::Software#os'
13
+ map_attribute :software_vendor, 'Sequel::ComputerComponent::Software#vendor'
14
+ map_attribute :game_os, 'Sequel::ComputerComponent::Software#os', :via => :game_id
15
+ map_attribute :game_vendor, 'Sequel::ComputerComponent::Software#vendor', :via => :game_id
16
+ end
@@ -76,5 +76,32 @@ ActiveRecord::Migration.suppress_messages do
76
76
  t.references :post
77
77
  t.timestamps
78
78
  end
79
+
80
+ create_table :computers do |t|
81
+ t.string :brand
82
+ t.references :game
83
+ t.timestamps
84
+ end
85
+
86
+ create_table :computer_component_hardwares do |t|
87
+ t.string :brand
88
+ t.string :cpu
89
+ t.integer :ram
90
+ t.integer :hdd
91
+ t.string :gfx
92
+ t.string :vendor
93
+ t.references :computer
94
+ t.timestamps
95
+ end
96
+
97
+ create_table :computer_component_softwares do |t|
98
+ t.string :os
99
+ t.string :vendor
100
+ t.references :osx
101
+ t.references :windows
102
+ t.references :linux
103
+ t.references :computer
104
+ t.timestamps
105
+ end
79
106
  end
80
107
  end
@@ -89,7 +89,40 @@ DB.create_table :authors do |t|
89
89
  primary_key :id
90
90
  String :name
91
91
  String :bio
92
- String :post_id
92
+ foreign_key :post_id
93
+ DateTime :created_at
94
+ DateTime :updated_at
95
+ end
96
+
97
+ DB.create_table :computers do |t|
98
+ primary_key :id
99
+ String :brand
100
+ foreign_key :game_id
101
+ DateTime :created_at
102
+ DateTime :updated_at
103
+ end
104
+
105
+ DB.create_table :computer_component_hardwares do |t|
106
+ primary_key :id
107
+ String :brand
108
+ String :cpu
109
+ Integer :ram
110
+ Integer :hdd
111
+ String :gfx
112
+ String :vendor
113
+ foreign_key :computer_id
114
+ DateTime :created_at
115
+ DateTime :updated_at
116
+ end
117
+
118
+ DB.create_table :computer_component_softwares do |t|
119
+ primary_key :id
120
+ String :os
121
+ String :vendor
122
+ foreign_key :computer_id
123
+ foreign_key :osx_id
124
+ foreign_key :windows_id
125
+ foreign_key :linux_id
93
126
  DateTime :created_at
94
127
  DateTime :updated_at
95
128
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datamappify
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.52.0
4
+ version: 0.53.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Wu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-26 00:00:00.000000000 Z
11
+ date: 2013-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: virtus
@@ -253,7 +253,7 @@ dependencies:
253
253
  - !ruby/object:Gem::Version
254
254
  version: 1.0.1
255
255
  description: Compose, decouple and manage domain logic and data persistence separately.
256
- Works great with forms!
256
+ Works particularly great for composing form objects!
257
257
  email:
258
258
  - ifredwu@gmail.com
259
259
  executables: []
@@ -349,6 +349,7 @@ files:
349
349
  - spec/repository/dirty_persistence_spec.rb
350
350
  - spec/repository/dirty_tracking_spec.rb
351
351
  - spec/repository/finders_spec.rb
352
+ - spec/repository/namespaced_mapping_spec.rb
352
353
  - spec/repository/persistence_spec.rb
353
354
  - spec/repository/reverse_mapped_spec.rb
354
355
  - spec/repository/transactions_spec.rb
@@ -359,8 +360,8 @@ files:
359
360
  - spec/support/entities/author.rb
360
361
  - spec/support/entities/comment.rb
361
362
  - spec/support/entities/computer.rb
362
- - spec/support/entities/computer_hardware.rb
363
- - spec/support/entities/computer_software.rb
363
+ - spec/support/entities/computer_component/hardware.rb
364
+ - spec/support/entities/computer_component/software.rb
364
365
  - spec/support/entities/group.rb
365
366
  - spec/support/entities/hero_user.rb
366
367
  - spec/support/entities/post.rb
@@ -370,6 +371,7 @@ files:
370
371
  - spec/support/repositories/active_record/admin_user_repository.rb
371
372
  - spec/support/repositories/active_record/author_repository.rb
372
373
  - spec/support/repositories/active_record/comment_repository.rb
374
+ - spec/support/repositories/active_record/computer_repository.rb
373
375
  - spec/support/repositories/active_record/group_repository.rb
374
376
  - spec/support/repositories/active_record/post_repository.rb
375
377
  - spec/support/repositories/active_record/role_repository.rb
@@ -379,6 +381,7 @@ files:
379
381
  - spec/support/repositories/sequel/admin_user_repository.rb
380
382
  - spec/support/repositories/sequel/author_repository.rb
381
383
  - spec/support/repositories/sequel/comment_repository.rb
384
+ - spec/support/repositories/sequel/computer_repository.rb
382
385
  - spec/support/repositories/sequel/group_repository.rb
383
386
  - spec/support/repositories/sequel/post_repository.rb
384
387
  - spec/support/repositories/sequel/role_repository.rb
@@ -413,7 +416,7 @@ rubygems_version: 2.0.3
413
416
  signing_key:
414
417
  specification_version: 4
415
418
  summary: Compose, decouple and manage domain logic and data persistence separately.
416
- Works great with forms!
419
+ Works particularly great for composing form objects!
417
420
  test_files:
418
421
  - spec/entity/composable_spec.rb
419
422
  - spec/entity/inheritance_spec.rb
@@ -424,6 +427,7 @@ test_files:
424
427
  - spec/repository/dirty_persistence_spec.rb
425
428
  - spec/repository/dirty_tracking_spec.rb
426
429
  - spec/repository/finders_spec.rb
430
+ - spec/repository/namespaced_mapping_spec.rb
427
431
  - spec/repository/persistence_spec.rb
428
432
  - spec/repository/reverse_mapped_spec.rb
429
433
  - spec/repository/transactions_spec.rb
@@ -434,8 +438,8 @@ test_files:
434
438
  - spec/support/entities/author.rb
435
439
  - spec/support/entities/comment.rb
436
440
  - spec/support/entities/computer.rb
437
- - spec/support/entities/computer_hardware.rb
438
- - spec/support/entities/computer_software.rb
441
+ - spec/support/entities/computer_component/hardware.rb
442
+ - spec/support/entities/computer_component/software.rb
439
443
  - spec/support/entities/group.rb
440
444
  - spec/support/entities/hero_user.rb
441
445
  - spec/support/entities/post.rb
@@ -445,6 +449,7 @@ test_files:
445
449
  - spec/support/repositories/active_record/admin_user_repository.rb
446
450
  - spec/support/repositories/active_record/author_repository.rb
447
451
  - spec/support/repositories/active_record/comment_repository.rb
452
+ - spec/support/repositories/active_record/computer_repository.rb
448
453
  - spec/support/repositories/active_record/group_repository.rb
449
454
  - spec/support/repositories/active_record/post_repository.rb
450
455
  - spec/support/repositories/active_record/role_repository.rb
@@ -454,6 +459,7 @@ test_files:
454
459
  - spec/support/repositories/sequel/admin_user_repository.rb
455
460
  - spec/support/repositories/sequel/author_repository.rb
456
461
  - spec/support/repositories/sequel/comment_repository.rb
462
+ - spec/support/repositories/sequel/computer_repository.rb
457
463
  - spec/support/repositories/sequel/group_repository.rb
458
464
  - spec/support/repositories/sequel/post_repository.rb
459
465
  - spec/support/repositories/sequel/role_repository.rb
@@ -1,12 +0,0 @@
1
- class ComputerHardware
2
- include Datamappify::Entity
3
-
4
- attribute :cpu, String
5
- attribute :ram, Integer, :default => 8192
6
- attribute :hdd, Integer, :default => 65536
7
- attribute :gfx, String
8
- attribute :vendor, String
9
-
10
- validates :ram, :hdd, :numericality => { :greater_than_or_equal_to => 4096 }
11
- validates :hdd, :numericality => { :less_than_or_equal_to => 65536 }
12
- end
@@ -1,12 +0,0 @@
1
- class ComputerSoftware
2
- include Datamappify::Entity
3
-
4
- attribute :os, String
5
- attribute :vendor, String
6
-
7
- references :osx
8
- references :windows
9
- references :linux
10
-
11
- validates_presence_of :os
12
- end