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
         
     |