rom-mapper 0.5.1 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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 Version](https://badge.fury.io/rb/rom-mapper.svg)][gem]
|
10
|
-
[![Build Status](https://travis-ci.org/rom-rb/rom-mapper.svg?branch=master)][travis]
|
11
7
|
[![Dependency Status](https://gemnasium.com/rom-rb/rom-mapper.svg)][gemnasium]
|
12
|
-
[![Code Climate](https://codeclimate.com/github/rom-rb/rom-mapper/badges/gpa.svg)][codeclimate]
|
13
|
-
[![Test Coverage](https://codeclimate.com/github/rom-rb/rom-mapper/badges/coverage.svg)][codeclimate]
|
14
|
-
[![Inline docs](http://inch-ci.org/github/rom-rb/rom-mapper.svg?branch=master)][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
|