rom 0.4.2 → 0.5.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +81 -0
  3. data/.travis.yml +2 -1
  4. data/CHANGELOG.md +41 -0
  5. data/Gemfile +12 -8
  6. data/Guardfile +17 -11
  7. data/README.md +7 -7
  8. data/Rakefile +29 -0
  9. data/lib/rom.rb +9 -66
  10. data/lib/rom/adapter.rb +45 -12
  11. data/lib/rom/adapter/memory.rb +0 -4
  12. data/lib/rom/adapter/memory/commands.rb +0 -10
  13. data/lib/rom/adapter/memory/dataset.rb +18 -6
  14. data/lib/rom/adapter/memory/storage.rb +0 -3
  15. data/lib/rom/command_registry.rb +24 -43
  16. data/lib/rom/commands.rb +5 -6
  17. data/lib/rom/commands/create.rb +5 -5
  18. data/lib/rom/commands/delete.rb +8 -6
  19. data/lib/rom/commands/result.rb +82 -0
  20. data/lib/rom/commands/update.rb +5 -4
  21. data/lib/rom/commands/with_options.rb +1 -4
  22. data/lib/rom/config.rb +70 -0
  23. data/lib/rom/env.rb +11 -3
  24. data/lib/rom/global.rb +107 -0
  25. data/lib/rom/header.rb +122 -89
  26. data/lib/rom/header/attribute.rb +148 -0
  27. data/lib/rom/mapper.rb +46 -67
  28. data/lib/rom/mapper_builder.rb +20 -73
  29. data/lib/rom/mapper_builder/mapper_dsl.rb +114 -0
  30. data/lib/rom/mapper_builder/model_dsl.rb +29 -0
  31. data/lib/rom/mapper_registry.rb +21 -0
  32. data/lib/rom/model_builder.rb +11 -17
  33. data/lib/rom/processor.rb +28 -0
  34. data/lib/rom/processor/transproc.rb +105 -0
  35. data/lib/rom/reader.rb +81 -21
  36. data/lib/rom/reader_builder.rb +14 -4
  37. data/lib/rom/relation.rb +19 -5
  38. data/lib/rom/relation_builder.rb +20 -6
  39. data/lib/rom/repository.rb +0 -2
  40. data/lib/rom/setup.rb +156 -0
  41. data/lib/rom/{boot → setup}/base_relation_dsl.rb +4 -8
  42. data/lib/rom/setup/command_dsl.rb +46 -0
  43. data/lib/rom/setup/finalize.rb +125 -0
  44. data/lib/rom/setup/mapper_dsl.rb +19 -0
  45. data/lib/rom/{boot → setup}/relation_dsl.rb +1 -4
  46. data/lib/rom/setup/schema_dsl.rb +33 -0
  47. data/lib/rom/support/registry.rb +10 -6
  48. data/lib/rom/version.rb +1 -1
  49. data/rom.gemspec +3 -1
  50. data/spec/integration/adapters/extending_relations_spec.rb +0 -2
  51. data/spec/integration/commands/create_spec.rb +2 -9
  52. data/spec/integration/commands/delete_spec.rb +4 -5
  53. data/spec/integration/commands/error_handling_spec.rb +4 -3
  54. data/spec/integration/commands/update_spec.rb +3 -8
  55. data/spec/integration/mappers/deep_embedded_spec.rb +52 -0
  56. data/spec/integration/mappers/definition_dsl_spec.rb +0 -118
  57. data/spec/integration/mappers/embedded_spec.rb +82 -0
  58. data/spec/integration/mappers/group_spec.rb +170 -0
  59. data/spec/integration/mappers/prefixing_attributes_spec.rb +2 -2
  60. data/spec/integration/mappers/renaming_attributes_spec.rb +8 -6
  61. data/spec/integration/mappers/symbolizing_attributes_spec.rb +80 -0
  62. data/spec/integration/mappers/wrap_spec.rb +162 -0
  63. data/spec/integration/multi_repo_spec.rb +64 -0
  64. data/spec/integration/relations/reading_spec.rb +12 -8
  65. data/spec/integration/relations/registry_dsl_spec.rb +1 -3
  66. data/spec/integration/schema_spec.rb +10 -0
  67. data/spec/integration/setup_spec.rb +57 -6
  68. data/spec/spec_helper.rb +2 -1
  69. data/spec/unit/config_spec.rb +60 -0
  70. data/spec/unit/rom/adapter/memory/dataset_spec.rb +52 -0
  71. data/spec/unit/rom/adapter_spec.rb +31 -11
  72. data/spec/unit/rom/header_spec.rb +60 -16
  73. data/spec/unit/rom/mapper_builder_spec.rb +311 -0
  74. data/spec/unit/rom/mapper_registry_spec.rb +25 -0
  75. data/spec/unit/rom/mapper_spec.rb +4 -5
  76. data/spec/unit/rom/model_builder_spec.rb +15 -13
  77. data/spec/unit/rom/processor/transproc_spec.rb +331 -0
  78. data/spec/unit/rom/reader_spec.rb +73 -0
  79. data/spec/unit/rom/registry_spec.rb +38 -0
  80. data/spec/unit/rom/relation_spec.rb +0 -1
  81. data/spec/unit/rom/setup_spec.rb +55 -0
  82. data/spec/unit/rom_spec.rb +14 -0
  83. metadata +62 -22
  84. data/Gemfile.devtools +0 -71
  85. data/lib/rom/boot.rb +0 -197
  86. data/lib/rom/boot/command_dsl.rb +0 -48
  87. data/lib/rom/boot/dsl.rb +0 -37
  88. data/lib/rom/boot/mapper_dsl.rb +0 -23
  89. data/lib/rom/boot/schema_dsl.rb +0 -27
  90. data/lib/rom/ra.rb +0 -172
  91. data/lib/rom/ra/operation/group.rb +0 -47
  92. data/lib/rom/ra/operation/join.rb +0 -39
  93. data/lib/rom/ra/operation/wrap.rb +0 -45
  94. data/lib/rom/transformer.rb +0 -77
  95. data/spec/integration/ra/group_spec.rb +0 -46
  96. data/spec/integration/ra/join_spec.rb +0 -50
  97. data/spec/integration/ra/wrap_spec.rb +0 -37
  98. data/spec/unit/rom/ra/operation/group_spec.rb +0 -55
  99. data/spec/unit/rom/ra/operation/wrap_spec.rb +0 -29
  100. data/spec/unit/rom/transformer_spec.rb +0 -41
@@ -0,0 +1,70 @@
1
+ module ROM
2
+ # Helper class used by ROM internally to deal with various configuration hashes
3
+ #
4
+ # @private
5
+ class Config
6
+ BASE_OPTIONS = [
7
+ :adapter,
8
+ :database,
9
+ :password,
10
+ :username,
11
+ :hostname,
12
+ :root
13
+ ].freeze
14
+
15
+ # Builds a configuration hash from a flat database config hash or a string
16
+ #
17
+ # This is used to support typical database.yml-complaint configs. It also
18
+ # uses adapter interface for things that are adapter-specific like handling
19
+ # schema naming.
20
+ #
21
+ # @param [Hash,String]
22
+ #
23
+ # @return [Hash]
24
+ #
25
+ # @api private
26
+ def self.build(config, options = {})
27
+ return config_hash(config, options) if config.is_a?(String)
28
+
29
+ return config unless config[:database]
30
+
31
+ root = config[:root]
32
+
33
+ raw_scheme = config[:adapter]
34
+ database = config[:database]
35
+ password = config.fetch(:password) { '' }
36
+ username = config[:username]
37
+ hostname = config.fetch(:hostname) { 'localhost' }
38
+
39
+ adapter = Adapter[raw_scheme]
40
+ scheme = adapter.normalize_scheme(raw_scheme)
41
+
42
+ path =
43
+ if adapter.database_file?(scheme)
44
+ [root, database].compact.join('/')
45
+ else
46
+ db_path = [hostname, database].join('/')
47
+
48
+ if username && password
49
+ [[username, password].join(':'), db_path].join('@')
50
+ else
51
+ db_path
52
+ end
53
+ end
54
+
55
+ other_keys = config.keys - BASE_OPTIONS
56
+ options = Hash[other_keys.zip(config.values_at(*other_keys))]
57
+
58
+ config_hash("#{scheme}://#{path}", options)
59
+ end
60
+
61
+ # @api private
62
+ def self.config_hash(uri, options = {})
63
+ if options.any?
64
+ { default: { uri: uri, options: options } }
65
+ else
66
+ { default: uri }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,11 +1,12 @@
1
1
  module ROM
2
-
3
2
  # Exposes defined repositories, schema, relations and mappers
4
3
  #
5
4
  # @api public
6
5
  class Env
7
6
  include Adamantium::Flat
8
- include Equalizer.new(:repositories, :schema, :relations, :mappers, :commands)
7
+ include Equalizer.new(
8
+ :repositories, :schema, :relations, :mappers, :commands
9
+ )
9
10
 
10
11
  attr_reader :repositories, :schema, :relations, :mappers, :commands
11
12
 
@@ -40,6 +41,10 @@ module ROM
40
41
  commands[name]
41
42
  end
42
43
 
44
+ # Return repository identified by its name
45
+ #
46
+ # @return [Repository]
47
+ #
43
48
  # @api private
44
49
  def [](name)
45
50
  repositories.fetch(name)
@@ -52,6 +57,10 @@ module ROM
52
57
 
53
58
  private
54
59
 
60
+ # Return repository if the method matches repository name
61
+ #
62
+ # @return [Repository]
63
+ #
55
64
  # @api private
56
65
  def method_missing(name, *args, &block)
57
66
  if repositories.key?(name)
@@ -61,5 +70,4 @@ module ROM
61
70
  end
62
71
  end
63
72
  end
64
-
65
73
  end
@@ -0,0 +1,107 @@
1
+ module ROM
2
+ # Globally accessible public interface exposed via ROM module
3
+ #
4
+ # @public
5
+ module Global
6
+ # Starts the setup process for schema, relations, mappers and commands
7
+ #
8
+ # @example
9
+ #
10
+ # ROM.setup('sqlite::memory')
11
+ #
12
+ # ROM.relation(:users) do
13
+ # # ...
14
+ # end
15
+ #
16
+ # ROM.mappers do
17
+ # define(:users) do
18
+ # # ...
19
+ # end
20
+ # end
21
+ #
22
+ # ROM.commands(:users) do
23
+ # define(:create) do
24
+ # # ...
25
+ # end
26
+ # end
27
+ #
28
+ # ROM.finalize # builds the env
29
+ # ROM.env # returns the env registry
30
+ #
31
+ # @param [Hash] options repository URIs
32
+ #
33
+ # @return [Setup] boot object
34
+ #
35
+ # @api public
36
+ def setup(*args, &block)
37
+ config = Config.build(*args)
38
+
39
+ adapters = config.each_with_object({}) do |(name, uri_or_opts), hash|
40
+ uri, opts =
41
+ if uri_or_opts.is_a?(Hash)
42
+ uri_or_opts.values_at(:uri, :options)
43
+ else
44
+ [uri_or_opts, {}]
45
+ end
46
+
47
+ hash[name] = Adapter.setup(uri, opts)
48
+ end
49
+
50
+ repositories = adapters.each_with_object({}) do |(name, adapter), hash|
51
+ hash[name] = Repository.new(adapter)
52
+ end
53
+
54
+ boot = Setup.new(repositories)
55
+
56
+ if block
57
+ boot.instance_exec(&block)
58
+ boot.finalize
59
+ else
60
+ @boot = boot
61
+ end
62
+ end
63
+
64
+ # @see ROM::Setup#schema
65
+ #
66
+ # @api public
67
+ def schema(&block)
68
+ boot.schema(&block)
69
+ end
70
+
71
+ # @see ROM::Setup#relation
72
+ #
73
+ # @api public
74
+ def relation(*args, &block)
75
+ boot.relation(*args, &block)
76
+ end
77
+
78
+ # @api public
79
+ def commands(*args, &block)
80
+ boot.commands(*args, &block)
81
+ end
82
+
83
+ # @api public
84
+ def mappers(*args, &block)
85
+ boot.mappers(*args, &block)
86
+ end
87
+
88
+ # @api public
89
+ def env
90
+ @env
91
+ end
92
+
93
+ # @api public
94
+ def finalize
95
+ @env = boot.finalize
96
+ @boot = nil
97
+ self
98
+ end
99
+
100
+ private
101
+
102
+ # @api private
103
+ def boot
104
+ @boot
105
+ end
106
+ end
107
+ end
@@ -1,123 +1,156 @@
1
- module ROM
1
+ require 'rom/header/attribute'
2
2
 
3
- # @api private
3
+ module ROM
4
+ # Header provides information about data mapping of a specific relation
5
+ #
6
+ # Processors use headers to build objects that process raw relations that go
7
+ # through mappers.
8
+ #
9
+ # @private
4
10
  class Header
5
11
  include Enumerable
6
- include Equalizer.new(:attributes)
12
+ include Equalizer.new(:attributes, :model)
7
13
 
14
+ # @api private
8
15
  attr_reader :attributes
9
16
 
10
- class Attribute
11
- include Equalizer.new(:name, :key, :type)
12
-
13
- attr_reader :name, :key, :meta
14
-
15
- class Embedded < Attribute
16
- include Equalizer.new(:name, :type, :model, :header)
17
-
18
- def model
19
- meta[:model]
20
- end
21
-
22
- def header
23
- meta.fetch(:header)
24
- end
25
-
26
- def mapping
27
- header.mapping
28
- end
29
-
30
- def embedded?
31
- true
32
- end
33
-
34
- end
35
-
36
- def self.[](type)
37
- if type == Array || type == Hash
38
- Embedded
39
- else
40
- self
41
- end
42
- end
43
-
44
- def self.coerce(input)
45
- if input.kind_of?(self)
46
- input
47
- else
48
- name = input[0]
49
- meta = (input[1] || {}).dup
50
-
51
- meta[:type] ||= Object
52
-
53
- if meta.key?(:header)
54
- meta[:header] = Header.coerce(meta[:header])
55
- end
56
-
57
- self[meta[:type]].new(name, meta)
58
- end
59
- end
60
-
61
- def initialize(name, meta = {})
62
- @name = name
63
- @meta = meta
64
- @key = meta.fetch(:from) { name }
65
- end
66
-
67
- def type
68
- meta.fetch(:type)
69
- end
70
-
71
- def aliased?
72
- key != name
73
- end
74
-
75
- def embedded?
76
- false
77
- end
78
-
79
- def mapping
80
- [key, name]
81
- end
82
- end
83
-
84
- def self.coerce(input)
85
- if input.kind_of?(self)
17
+ # @return [Class] optional model associated with a header
18
+ #
19
+ # @api private
20
+ attr_reader :model
21
+
22
+ # @return [Hash] attribute key/name mapping for all primitive attributes
23
+ #
24
+ # @api private
25
+ attr_reader :mapping
26
+
27
+ # @return [Array] all attribute keys that are in a tuple
28
+ #
29
+ # @api private
30
+ attr_reader :tuple_keys
31
+
32
+ # Coerce array with attribute definitions into a header object
33
+ #
34
+ # @param [Array<Array>] attribute name/option pairs
35
+ #
36
+ # @param [Class] optional model
37
+ #
38
+ # @return [Header]
39
+ #
40
+ # @api private
41
+ def self.coerce(input, model = nil)
42
+ if input.instance_of?(self)
86
43
  input
87
44
  else
88
45
  attributes = input.each_with_object({}) { |pair, h|
89
46
  h[pair.first] = Attribute.coerce(pair)
90
47
  }
91
48
 
92
- new(attributes)
49
+ new(attributes, model)
93
50
  end
94
51
  end
95
52
 
96
- def initialize(attributes)
53
+ # @api private
54
+ def initialize(attributes, model = nil)
97
55
  @attributes = attributes
56
+ @model = model
57
+ initialize_mapping
58
+ initialize_tuple_keys
98
59
  end
99
60
 
61
+ # Iterate over attributes
62
+ #
63
+ # @yield [Attribute]
64
+ #
65
+ # @api private
100
66
  def each(&block)
101
- return to_enum unless block
102
67
  attributes.values.each(&block)
103
68
  end
104
69
 
70
+ # Return if there are any aliased attributes
71
+ #
72
+ # @api private
73
+ def aliased?
74
+ any?(&:aliased?)
75
+ end
76
+
77
+ # Return attribute keys
78
+ #
79
+ # An attribute key corresponds to tuple attribute names
80
+ #
81
+ # @api private
105
82
  def keys
106
83
  attributes.keys
107
84
  end
108
85
 
109
- def mapping
110
- Hash[select { |a| !a.embedded? }.map(&:mapping)]
86
+ # Return attribute identified by its name
87
+ #
88
+ # @return [Attribute]
89
+ #
90
+ # @api private
91
+ def [](name)
92
+ attributes.fetch(name)
111
93
  end
112
94
 
113
- def values
114
- attributes.values
95
+ # Return all Group attributes
96
+ #
97
+ # @return [Array<Group>]
98
+ #
99
+ # @api private
100
+ def groups
101
+ by_type(Group)
115
102
  end
116
103
 
117
- def [](name)
118
- attributes.fetch(name)
104
+ # Return all Wrap attributes
105
+ #
106
+ # @return [Array<Wrap>]
107
+ #
108
+ # @api private
109
+ def wraps
110
+ by_type(Wrap)
119
111
  end
120
112
 
121
- end
113
+ # Return all primitive attributes (no Group and Wrap)
114
+ #
115
+ # @return [Array<Attribute>]
116
+ #
117
+ # @api private
118
+ def primitives
119
+ to_a - non_primitives
120
+ end
121
+
122
+ # Return all non-primitive attributes (only Group and Wrap types)
123
+ #
124
+ # @return [Array<Group,Wrap>]
125
+ #
126
+ # @api private
127
+ def non_primitives
128
+ groups + wraps
129
+ end
130
+
131
+ private
122
132
 
133
+ # Find all attribute matching specific attribute class (not kind)
134
+ #
135
+ # @return [Array<Attribute>]
136
+ #
137
+ # @api private
138
+ def by_type(*types)
139
+ select { |attribute| types.include?(attribute.class) }
140
+ end
141
+
142
+ # Set mapping hash from primitive attributes
143
+ #
144
+ # @api private
145
+ def initialize_mapping
146
+ @mapping = primitives.map(&:mapping).reduce(:merge) || {}
147
+ end
148
+
149
+ # Set all tuple keys from all attributes going deep into Wrap and Group too
150
+ #
151
+ # @api private
152
+ def initialize_tuple_keys
153
+ @tuple_keys = mapping.keys + non_primitives.map(&:tuple_keys).flatten
154
+ end
155
+ end
123
156
  end