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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 711c240df14665593d9d75aab68c4894ef711a36
4
- data.tar.gz: 96fdecc56bba304f3b088d86ada06f4cce5ed490
3
+ metadata.gz: dddebe835c3ce5f41a86e813e8e13334fa4a97dd
4
+ data.tar.gz: 2e241396a5a571534f25ed5cc5fbc171295b4a55
5
5
  SHA512:
6
- metadata.gz: b39af8bbfdb4ce683c1a110cce29188aa6047be279161d07221668e3f8c4ea53e855e941e1238211dc2debf0b5256a2c27094bc08cf2e4b4eab5cd39d1756d91
7
- data.tar.gz: 135de91ef60a3e2dfcf9fa9ea681ce77aed9efeada23c2fca013d3142428a66af9f8a879b45d8e8d940e6b30000afc4e4008289d8e9c873624452b452b373f9a
6
+ metadata.gz: a664f6746a7b14d2b8d57898bf3bc6376fae7c56e9360ad4b39eaa54efdce1616415f401202a921292ee0032eacdb457d723ed2621a10ad7ab98c8d5210cba09
7
+ data.tar.gz: 8b6228e180646d9eea2ab4e66fd83ef8807084f57da996df14ba00e4386e891f6ace3322a38aa6eefbc422ab3b235f6d1355487d3f4b010f1fe4689905e69976
@@ -1,3 +1,11 @@
1
+ # 0.5.1 2017-05-04
2
+
3
+ ### Changed
4
+
5
+ * The `dry-core` dependency has been relaxed (flash-gordon)
6
+
7
+ [Compare v0.5.0..v0.5.1](https://github.com/rom-rb/rom-mapper/compare/v0.5.0...v0.5.1)
8
+
1
9
  # 0.5.0 2017-01-29
2
10
 
3
11
  ### Changed
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Ruby Object Mapper Team
1
+ Copyright (c) 2013-2017 rom-rb team
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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
- - [Guides](http://www.rubydoc.info/gems/rom-mapper/0.3.0/ROM/Mapper/AttributeDSL)
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
 
@@ -1,4 +1,4 @@
1
- require 'dry-equalizer'
1
+ require 'dry/equalizer'
2
2
 
3
3
  require 'rom/mapper'
4
4
  require 'rom/processor/transproc'
@@ -1,3 +1,5 @@
1
+ require 'dry/equalizer'
2
+
1
3
  module ROM
2
4
  class Header
3
5
  # An attribute provides information about a specific attribute in a tuple
@@ -1,12 +1,12 @@
1
- require 'dry/core/constants'
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
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  class Mapper
3
- VERSION = '0.5.1'.freeze
3
+ VERSION = '1.0.0.beta1'.freeze
4
4
  end
5
5
  end
@@ -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) >> -> arr { arr.first }
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, -> arr { arr.first })
176
+ t(:map_value, attribute.name, t(:get, 0))
173
177
  end
174
178
  end
175
179
 
@@ -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