rom 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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