rom-mapper 0.5.1 → 1.0.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/LICENSE +1 -1
- data/README.md +1 -9
- data/lib/rom-mapper.rb +1 -1
- data/lib/rom/header/attribute.rb +2 -0
- data/lib/rom/mapper.rb +2 -2
- data/lib/rom/mapper/builder.rb +37 -0
- data/lib/rom/mapper/configuration_plugin.rb +26 -0
- data/lib/rom/mapper/mapper_dsl.rb +43 -0
- data/lib/rom/mapper/version.rb +1 -1
- data/lib/rom/mapper_compiler.rb +71 -0
- data/lib/rom/open_struct.rb +35 -0
- data/lib/rom/processor/transproc.rb +6 -2
- data/lib/rom/struct.rb +113 -0
- data/lib/rom/struct_compiler.rb +94 -0
- metadata +19 -31
- data/.gitignore +0 -19
- data/.rspec +0 -3
- data/.ruby-gemset +0 -1
- data/.travis.yml +0 -23
- data/Gemfile +0 -31
- data/Guardfile +0 -19
- data/Rakefile +0 -16
- data/rakelib/benchmark.rake +0 -15
- data/rakelib/mutant.rake +0 -16
- data/rakelib/rubocop.rake +0 -18
- data/rom-mapper.gemspec +0 -22
- data/spec/integration/mapper_spec.rb +0 -113
- data/spec/spec_helper.rb +0 -57
- data/spec/support/constant_leak_finder.rb +0 -14
- data/spec/support/mutant.rb +0 -10
- data/spec/unit/rom/mapper/dsl_spec.rb +0 -479
- data/spec/unit/rom/mapper/model_dsl_spec.rb +0 -19
- data/spec/unit/rom/mapper_spec.rb +0 -83
- data/spec/unit/rom/processor/transproc_spec.rb +0 -506
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dddebe835c3ce5f41a86e813e8e13334fa4a97dd
|
4
|
+
data.tar.gz: 2e241396a5a571534f25ed5cc5fbc171295b4a55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a664f6746a7b14d2b8d57898bf3bc6376fae7c56e9360ad4b39eaa54efdce1616415f401202a921292ee0032eacdb457d723ed2621a10ad7ab98c8d5210cba09
|
7
|
+
data.tar.gz: 8b6228e180646d9eea2ab4e66fd83ef8807084f57da996df14ba00e4386e891f6ace3322a38aa6eefbc422ab3b235f6d1355487d3f4b010f1fe4689905e69976
|
data/CHANGELOG.md
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,25 +1,17 @@
|
|
1
1
|
[gem]: https://rubygems.org/gems/rom-mapper
|
2
|
-
[travis]: https://travis-ci.org/rom-rb/rom-mapper
|
3
2
|
[gemnasium]: https://gemnasium.com/rom-rb/rom-mapper
|
4
|
-
[codeclimate]: https://codeclimate.com/github/rom-rb/rom-mapper
|
5
|
-
[inchpages]: http://inch-ci.org/github/rom-rb/rom-mapper
|
6
3
|
|
7
4
|
# rom-mapper
|
8
5
|
|
9
6
|
[][gem]
|
10
|
-
[][travis]
|
11
7
|
[][gemnasium]
|
12
|
-
[][codeclimate]
|
13
|
-
[][codeclimate]
|
14
|
-
[][inchpages]
|
15
8
|
|
16
9
|
ROM mapper component is a DSL for defining object mappers with pluggable mapping
|
17
10
|
backends. It is the default mapper in ROM.
|
18
11
|
|
19
12
|
Resources:
|
20
13
|
|
21
|
-
- [
|
22
|
-
- [Importing Data with ROM and Transproc](http://solnic.eu/2015/07/15/importing-data-with-rom-and-transproc.html)
|
14
|
+
- [API documentation](http://rubydoc.info/gems/rom-mapper)
|
23
15
|
|
24
16
|
## License
|
25
17
|
|
data/lib/rom-mapper.rb
CHANGED
data/lib/rom/header/attribute.rb
CHANGED
data/lib/rom/mapper.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
require '
|
1
|
+
require 'rom/constants'
|
2
2
|
require 'rom/mapper/dsl'
|
3
|
+
require 'rom/mapper/configuration_plugin'
|
3
4
|
|
4
5
|
module ROM
|
5
6
|
# Mapper is a simple object that uses transformers to load relations
|
6
7
|
#
|
7
8
|
# @private
|
8
9
|
class Mapper
|
9
|
-
include Dry::Core::Constants
|
10
10
|
include DSL
|
11
11
|
include Dry::Equalizer(:transformers, :header)
|
12
12
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'dry/core/class_builder'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Mapper
|
5
|
+
# Setup DSL-specific mapper extensions
|
6
|
+
#
|
7
|
+
# @private
|
8
|
+
class Builder
|
9
|
+
# Generate a mapper subclass
|
10
|
+
#
|
11
|
+
# This is used by Setup#mappers DSL
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
def self.build_class(name, mapper_registry, options = EMPTY_HASH, &block)
|
15
|
+
class_name = "ROM::Mapper[#{name}]"
|
16
|
+
|
17
|
+
parent = options[:parent]
|
18
|
+
inherit_header = options.fetch(:inherit_header) { ROM::Mapper.inherit_header }
|
19
|
+
|
20
|
+
parent_class =
|
21
|
+
if parent
|
22
|
+
mapper_registry.detect { |klass| klass.relation == parent }
|
23
|
+
else
|
24
|
+
ROM::Mapper
|
25
|
+
end
|
26
|
+
|
27
|
+
Dry::Core::ClassBuilder.new(name: class_name, parent: parent_class).call do |klass|
|
28
|
+
klass.register_as(name)
|
29
|
+
klass.relation(name)
|
30
|
+
klass.inherit_header(inherit_header)
|
31
|
+
|
32
|
+
klass.class_eval(&block) if block
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rom/mapper/mapper_dsl'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Mapper
|
5
|
+
# Model DSL allows setting a model class
|
6
|
+
#
|
7
|
+
# @private
|
8
|
+
module ConfigurationPlugin
|
9
|
+
# Mapper definition DSL used by Setup DSL
|
10
|
+
#
|
11
|
+
# @private
|
12
|
+
|
13
|
+
def self.apply(configuration, options = {})
|
14
|
+
configuration.extend Methods
|
15
|
+
configuration
|
16
|
+
end
|
17
|
+
|
18
|
+
module Methods
|
19
|
+
def mappers(&block)
|
20
|
+
register_mapper(*MapperDSL.new(self, mapper_classes, block).mapper_classes)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rom/mapper/builder'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Mapper
|
5
|
+
# Mapper definition DSL used by Setup DSL
|
6
|
+
#
|
7
|
+
# @private
|
8
|
+
class MapperDSL
|
9
|
+
attr_reader :configuration, :mapper_classes, :defined_mappers
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
def initialize(configuration, mapper_classes, block)
|
13
|
+
@configuration = configuration
|
14
|
+
@mapper_classes = mapper_classes
|
15
|
+
@defined_mappers = []
|
16
|
+
|
17
|
+
instance_exec(&block)
|
18
|
+
|
19
|
+
@mapper_classes = @defined_mappers
|
20
|
+
end
|
21
|
+
|
22
|
+
# Define a mapper class
|
23
|
+
#
|
24
|
+
# @param [Symbol] name of the mapper
|
25
|
+
# @param [Hash] options
|
26
|
+
#
|
27
|
+
# @return [Class]
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
def define(name, options = EMPTY_HASH, &block)
|
31
|
+
@defined_mappers << Builder.build_class(name, (@mapper_classes + @defined_mappers), options, &block)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def register(relation, mappers)
|
39
|
+
configuration.register_mapper(relation => mappers)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/rom/mapper/version.rb
CHANGED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rom/initializer'
|
2
|
+
require 'rom/mapper'
|
3
|
+
require 'rom/struct'
|
4
|
+
require 'rom/struct_compiler'
|
5
|
+
require 'rom/cache'
|
6
|
+
|
7
|
+
module ROM
|
8
|
+
# @api private
|
9
|
+
class MapperCompiler
|
10
|
+
extend Initializer
|
11
|
+
|
12
|
+
option :cache, reader: true, default: -> { Cache.new }
|
13
|
+
|
14
|
+
attr_reader :struct_compiler
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
super
|
18
|
+
@struct_compiler = StructCompiler.new(cache: cache)
|
19
|
+
@cache = cache.namespaced(:mappers)
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(ast)
|
23
|
+
cache.fetch_or_store(ast.hash) { Mapper.build(Header.coerce(*visit(ast))) }
|
24
|
+
end
|
25
|
+
alias_method :[], :call
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def visit(node)
|
30
|
+
name, node = node
|
31
|
+
__send__("visit_#{name}", node)
|
32
|
+
end
|
33
|
+
|
34
|
+
def visit_relation(node)
|
35
|
+
rel_name, header, meta = node
|
36
|
+
name = meta[:combine_name] || meta[:alias] || rel_name
|
37
|
+
namespace = meta.fetch(:struct_namespace)
|
38
|
+
|
39
|
+
model = meta.fetch(:model) do
|
40
|
+
if meta[:combine_name]
|
41
|
+
false
|
42
|
+
else
|
43
|
+
struct_compiler[name, header, namespace]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
options = [header.map(&method(:visit)), model: model]
|
48
|
+
|
49
|
+
if meta[:combine_type]
|
50
|
+
type = meta[:combine_type] == :many ? :array : :hash
|
51
|
+
keys = meta.fetch(:keys)
|
52
|
+
|
53
|
+
[name, combine: true, type: type, keys: keys, header: Header.coerce(*options)]
|
54
|
+
elsif meta[:wrap]
|
55
|
+
[name, wrap: true, type: :hash, header: Header.coerce(*options)]
|
56
|
+
else
|
57
|
+
options
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def visit_attribute(node)
|
62
|
+
name, _, meta = node
|
63
|
+
|
64
|
+
if meta[:wrapped]
|
65
|
+
[name, from: meta[:alias]]
|
66
|
+
else
|
67
|
+
[name]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ROM
|
2
|
+
# ROM's open structs are used for relations with empty schemas.
|
3
|
+
# Such relations may exist in cases like using raw SQL strings
|
4
|
+
# where schema was not explicitly defined using `view` DSL.
|
5
|
+
#
|
6
|
+
# @api public
|
7
|
+
class OpenStruct
|
8
|
+
IVAR = -> v { :"@#{v}" }
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
def initialize(attributes)
|
12
|
+
attributes.each do |key, value|
|
13
|
+
instance_variable_set(IVAR[key], value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
def respond_to_missing?(meth, include_private = false)
|
19
|
+
super || instance_variables.include?(IVAR[meth])
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
def method_missing(meth, *args, &block)
|
26
|
+
ivar = IVAR[meth]
|
27
|
+
|
28
|
+
if instance_variables.include?(ivar)
|
29
|
+
instance_variable_get(ivar)
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -28,6 +28,10 @@ module ROM
|
|
28
28
|
tuple
|
29
29
|
end
|
30
30
|
|
31
|
+
def self.get(arr, idx)
|
32
|
+
arr[idx]
|
33
|
+
end
|
34
|
+
|
31
35
|
def self.filter_empty(arr)
|
32
36
|
arr.reject { |row| row.values.all?(&:nil?) }
|
33
37
|
end
|
@@ -158,7 +162,7 @@ module ROM
|
|
158
162
|
op = with_row_proc(attribute) do |row_proc|
|
159
163
|
array_proc =
|
160
164
|
if attribute.type == :hash
|
161
|
-
t(:map_array, row_proc) >>
|
165
|
+
t(:map_array, row_proc) >> t(:get, 0)
|
162
166
|
else
|
163
167
|
t(:map_array, row_proc)
|
164
168
|
end
|
@@ -169,7 +173,7 @@ module ROM
|
|
169
173
|
if op
|
170
174
|
op
|
171
175
|
elsif attribute.type == :hash
|
172
|
-
t(:map_value, attribute.name,
|
176
|
+
t(:map_value, attribute.name, t(:get, 0))
|
173
177
|
end
|
174
178
|
end
|
175
179
|
|
data/lib/rom/struct.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'dry/struct'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
# Simple data-struct class
|
5
|
+
#
|
6
|
+
# ROM structs are plain data structures loaded by repositories.
|
7
|
+
# They implement Hash protocol which means that they can be used
|
8
|
+
# in places where Hash-like objects are supported.
|
9
|
+
#
|
10
|
+
# Repositories define subclasses of ROM::Struct automatically, they are
|
11
|
+
# defined in the ROM::Struct namespace by default, but you set it up
|
12
|
+
# to use your namespace/module as well.
|
13
|
+
#
|
14
|
+
# Structs are based on dry-struct gem, they include `schema` with detailed information
|
15
|
+
# about attribute types returned from relations, thus can be introspected to build
|
16
|
+
# additional functionality when desired.
|
17
|
+
#
|
18
|
+
# There is a caveat you should know about when working with structs. Struct classes
|
19
|
+
# have names but at the same time they're anonymous, i.e. you can't get the User struct class
|
20
|
+
# with ROM::Struct::User. ROM will create as many struct classes for User as needed,
|
21
|
+
# they all will have the same name and ROM::Struct::User will be the common parent class for
|
22
|
+
# them. Combined with the ability to provide your own namespace for structs this enables to
|
23
|
+
# pre-define the parent class.
|
24
|
+
#
|
25
|
+
# @example accessing relation struct model
|
26
|
+
# rom = ROM.container(:sql, 'sqlite::memory') do |conf|
|
27
|
+
# conf.default.create_table(:users) do
|
28
|
+
# primary_key :id
|
29
|
+
# column :name, String
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# class UserRepo < ROM::Repository[:users]
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# user_repo = UserRepo.new(rom)
|
37
|
+
#
|
38
|
+
# # get auto-generated User struct
|
39
|
+
# model = user_repo.users.mapper.model
|
40
|
+
# # => ROM::Struct::User
|
41
|
+
#
|
42
|
+
# # see struct's schema attributes
|
43
|
+
#
|
44
|
+
# # model.schema[:id]
|
45
|
+
# # => #<Dry::Types::Constrained type=#<Dry::Types::Definition primitive=Integer options={}> options={:rule=>#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#gt?> options={:args=>[0]}>, :meta=>{:primary_key=>true, :name=>:id, :source=>ROM::Relation::Name(users)}} rule=#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#gt?> options={:args=>[0]}>>
|
46
|
+
#
|
47
|
+
# model.schema[:name]
|
48
|
+
# # => #<Dry::Types::Sum left=#<Dry::Types::Constrained type=#<Dry::Types::Definition primitive=NilClass options={}> options={:rule=>#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#type?> options={:args=>[NilClass]}>} rule=#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#type?> options={:args=>[NilClass]}>> right=#<Dry::Types::Definition primitive=String options={}> options={:meta=>{:name=>:name, :source=>ROM::Relation::Name(users)}}>
|
49
|
+
#
|
50
|
+
# @example passing a namespace with an existing parent class
|
51
|
+
# module Entities
|
52
|
+
# class User < ROM::Struct
|
53
|
+
# def upcased_name
|
54
|
+
# name.upcase
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# class UserRepo < ROM::Repository[:users]
|
60
|
+
# struct_namespace Entities
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# user_repo = UserRepo.new(rom)
|
64
|
+
# user = user_repo.users.by_pk(1).one!
|
65
|
+
# user.name # => "Jane"
|
66
|
+
# user.upcased_name # => "JANE"
|
67
|
+
#
|
68
|
+
# @see http://dry-rb.org/gems/dry-struct dry-struct
|
69
|
+
# @see http://dry-rb.org/gems/dry-types dry-types
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
class Struct < Dry::Struct
|
73
|
+
MissingAttribute = Class.new(NameError)
|
74
|
+
|
75
|
+
# Returns a short string representation
|
76
|
+
#
|
77
|
+
# @return [String]
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
def to_s
|
81
|
+
"#<#{self.class}:0x#{(object_id << 1).to_s(16)}>"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Return attribute value
|
85
|
+
#
|
86
|
+
# @param [Symbol] name The attribute name
|
87
|
+
#
|
88
|
+
# @api public
|
89
|
+
def fetch(name)
|
90
|
+
__send__(name)
|
91
|
+
end
|
92
|
+
|
93
|
+
if RUBY_VERSION < '2.3'
|
94
|
+
# @api private
|
95
|
+
def respond_to?(*)
|
96
|
+
super
|
97
|
+
end
|
98
|
+
else
|
99
|
+
# @api private
|
100
|
+
def respond_to_missing?(*)
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def method_missing(*)
|
108
|
+
super
|
109
|
+
rescue NameError => error
|
110
|
+
raise MissingAttribute.new("#{ error.message } (not loaded attribute?)")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|