flatter 0.1.0 → 0.2.0
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/README.md +679 -5
- data/flatter.gemspec +1 -0
- data/lib/flatter/mapper/attribute_methods.rb +72 -5
- data/lib/flatter/mapper/collection.rb +193 -0
- data/lib/flatter/mapper/factory.rb +24 -4
- data/lib/flatter/mapper/mapping.rb +2 -4
- data/lib/flatter/mapper/mounting.rb +37 -13
- data/lib/flatter/mapper/options.rb +1 -1
- data/lib/flatter/mapper/persistence.rb +11 -2
- data/lib/flatter/mapper/target.rb +53 -7
- data/lib/flatter/mapper/traits.rb +12 -23
- data/lib/flatter/mapper/write_with_indifferent_access.rb +8 -0
- data/lib/flatter/mapper.rb +6 -2
- data/lib/flatter/version.rb +1 -1
- metadata +18 -2
    
        data/flatter.gemspec
    CHANGED
    
    | @@ -29,6 +29,7 @@ Gem::Specification.new do |spec| | |
| 29 29 | 
             
              spec.add_development_dependency "bundler", "~> 1.10"
         | 
| 30 30 | 
             
              spec.add_development_dependency "rake", "~> 10.0"
         | 
| 31 31 | 
             
              spec.add_development_dependency "rspec"
         | 
| 32 | 
            +
              spec.add_development_dependency "rspec-its"
         | 
| 32 33 | 
             
              spec.add_development_dependency "simplecov", ">= 0.9"
         | 
| 33 34 | 
             
              spec.add_development_dependency "pry"
         | 
| 34 35 | 
             
              spec.add_development_dependency "pry-nav"
         | 
| @@ -1,7 +1,8 @@ | |
| 1 1 | 
             
            module Flatter
         | 
| 2 2 | 
             
              module Mapper::AttributeMethods
         | 
| 3 3 | 
             
                def respond_to_missing?(name, *)
         | 
| 4 | 
            -
                  mapping_names.map{ |name| [name,  | 
| 4 | 
            +
                  acceptable = mapping_names.map{ |name| [name, "#{name}="] }.flatten + mounting_names
         | 
| 5 | 
            +
                  acceptable.uniq.map(&:to_sym).include?(name) || super
         | 
| 5 6 | 
             
                end
         | 
| 6 7 |  | 
| 7 8 | 
             
                def method_missing(name, *args, &block)
         | 
| @@ -13,13 +14,79 @@ module Flatter | |
| 13 14 | 
             
                  send(name, *args, &block)
         | 
| 14 15 | 
             
                end
         | 
| 15 16 |  | 
| 17 | 
            +
                def mounting(name)
         | 
| 18 | 
            +
                  find_mounting(name.to_s)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def find_mounting(name)
         | 
| 22 | 
            +
                  local_mountings.each do |mounting|
         | 
| 23 | 
            +
                    if mounting.name == name || (mounting.pluralized? && mounting.name.pluralize == name)
         | 
| 24 | 
            +
                      return mounting
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                    nested = mounting.find_mounting(name)
         | 
| 27 | 
            +
                    return nested if nested.present?
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                  nil
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
                protected :find_mounting
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def find_mounting_with(mapping_name)
         | 
| 34 | 
            +
                  mapping_name = mapping_name.to_s
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  match = local_mappings.any? do |mapping|
         | 
| 37 | 
            +
                    if collection? || pluralized?
         | 
| 38 | 
            +
                      mapping.name.pluralize == mapping_name
         | 
| 39 | 
            +
                    else
         | 
| 40 | 
            +
                      mapping.name == mapping_name
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  return self if match
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  local_mountings.each do |mounting|
         | 
| 47 | 
            +
                    nested = mounting.find_mounting_with(mapping_name)
         | 
| 48 | 
            +
                    return nested if nested.present?
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  nil
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                protected :find_mounting_with
         | 
| 54 | 
            +
             | 
| 16 55 | 
             
                def attribute_methods
         | 
| 17 | 
            -
                   | 
| 56 | 
            +
                  _mapping_names = mapping_names
         | 
| 57 | 
            +
                  _mounting_names = mounting_names - _mapping_names
         | 
| 58 | 
            +
             | 
| 18 59 | 
             
                  Module.new do
         | 
| 19 | 
            -
                     | 
| 20 | 
            -
                      define_method(name) | 
| 60 | 
            +
                    _mounting_names.each do |name|
         | 
| 61 | 
            +
                      define_method(name) do
         | 
| 62 | 
            +
                        mount = find_mounting(name)
         | 
| 63 | 
            +
                        if mount.collection?
         | 
| 64 | 
            +
                          mount.read[name.to_s]
         | 
| 65 | 
            +
                        elsif mount.pluralized?
         | 
| 66 | 
            +
                          Array(mountings[mount.name]).map(&:read)
         | 
| 67 | 
            +
                        else
         | 
| 68 | 
            +
                          mount.read
         | 
| 69 | 
            +
                        end
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    _mapping_names.each do |name|
         | 
| 74 | 
            +
                      define_method(name) do |*args|
         | 
| 75 | 
            +
                        mount = find_mounting_with(name)
         | 
| 76 | 
            +
                        if mount.collection? || mount.pluralized?
         | 
| 77 | 
            +
                          Array(mapping(name.singularize)).map{ |map| map.read(*args) }
         | 
| 78 | 
            +
                        else
         | 
| 79 | 
            +
                          mapping(name).read(*args)
         | 
| 80 | 
            +
                        end
         | 
| 81 | 
            +
                      end
         | 
| 21 82 |  | 
| 22 | 
            -
                      define_method( | 
| 83 | 
            +
                      define_method("#{name}=") do |value|
         | 
| 84 | 
            +
                        mount = find_mounting_with(name)
         | 
| 85 | 
            +
                        if mount.collection? || mount.pluralized?
         | 
| 86 | 
            +
                          fail RuntimeError, "Cannot directly write to a collection"
         | 
| 87 | 
            +
                        end
         | 
| 88 | 
            +
                        mapping(name).write(value)
         | 
| 89 | 
            +
                      end
         | 
| 23 90 | 
             
                    end
         | 
| 24 91 | 
             
                  end
         | 
| 25 92 | 
             
                end
         | 
| @@ -0,0 +1,193 @@ | |
| 1 | 
            +
            module Flatter
         | 
| 2 | 
            +
              module Mapper::Collection
         | 
| 3 | 
            +
                NonUniqKeysError = Class.new(RuntimeError)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def self.prepended(base)
         | 
| 6 | 
            +
                  base.send(:include, Concern)
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                module FactoryMethods
         | 
| 10 | 
            +
                  def create(*)
         | 
| 11 | 
            +
                    super.tap do |mapper|
         | 
| 12 | 
            +
                      mapper.options.merge!(collection: collection?)
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def default_mapper_class_name
         | 
| 17 | 
            +
                    collection? ? "#{name.singularize.camelize}Mapper" : super
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def collection?
         | 
| 21 | 
            +
                    options[:collection] == true ||
         | 
| 22 | 
            +
                      (options[:collection] != false && name == name.pluralize)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                module Concern
         | 
| 27 | 
            +
                  extend ActiveSupport::Concern
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  included do
         | 
| 30 | 
            +
                    mapper_options.push(:collection, :item_index)
         | 
| 31 | 
            +
                    attr_accessor :item_index
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  module ClassMethods
         | 
| 35 | 
            +
                    def key(arg = nil)
         | 
| 36 | 
            +
                      args    = []
         | 
| 37 | 
            +
                      options = {writer: false}
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      case arg
         | 
| 40 | 
            +
                      when String, Symbol
         | 
| 41 | 
            +
                        options[:key] = arg.to_sym
         | 
| 42 | 
            +
                      when Proc
         | 
| 43 | 
            +
                        args << :key
         | 
| 44 | 
            +
                        options[:reader] = arg
         | 
| 45 | 
            +
                      else
         | 
| 46 | 
            +
                        fail ArgumentError, "Cannot use '#{arg}' as collection key"
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      map *args, **options
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  def remove_items(keys)
         | 
| 54 | 
            +
                    collection.reject! do |item|
         | 
| 55 | 
            +
                      (item[:key].nil? || keys.include?(item[:key])) &&
         | 
| 56 | 
            +
                        delete_target_item(item.target)
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                  private :remove_items
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def delete_target_item(item)
         | 
| 62 | 
            +
                    !!target.delete(item)
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def update_item(key, params)
         | 
| 66 | 
            +
                    collection.find{ |item| item[:key] == key }.write(params)
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  def add_item(params)
         | 
| 70 | 
            +
                    collection << clone.tap do |mapper|
         | 
| 71 | 
            +
                      item = target_class.new
         | 
| 72 | 
            +
                      mapper.reset_locals!
         | 
| 73 | 
            +
                      mapper.set_target!(item)
         | 
| 74 | 
            +
                      mapper.item_index = collection.length
         | 
| 75 | 
            +
                      mapper.write(params)
         | 
| 76 | 
            +
                      add_target_item(item)
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def add_target_item(item)
         | 
| 81 | 
            +
                    target << item
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def read
         | 
| 86 | 
            +
                  return super unless collection?
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  values = collection.map(&:read)
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  assert_key_uniqueness!(values)
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  {name => values}
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def write(params)
         | 
| 96 | 
            +
                  return super unless collection?
         | 
| 97 | 
            +
                  return unless params.key?(name)
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  data = params[name]
         | 
| 100 | 
            +
                  assert_collection!(data)
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  keys = collection.map(&:key)
         | 
| 103 | 
            +
                  remove_items(keys - data.map{ |p| p[:key] })
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  data.each do |params|
         | 
| 106 | 
            +
                    if params.key?(:key)
         | 
| 107 | 
            +
                      update_item(params[:key], params.except(:key))
         | 
| 108 | 
            +
                    else
         | 
| 109 | 
            +
                      add_item(params)
         | 
| 110 | 
            +
                    end
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                def pluralize!
         | 
| 115 | 
            +
                  @_pluralized = true
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                def pluralized?
         | 
| 119 | 
            +
                  !!@_pluralized
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                def mapping_names
         | 
| 123 | 
            +
                  super.map{ |name| collection? || pluralized? ? name.pluralize : name }
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                def mounting_names
         | 
| 127 | 
            +
                  super.map{ |name| pluralized? ? name.pluralize : name }
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                def local_mountings
         | 
| 131 | 
            +
                  super.each{ |mapper| mapper.pluralize! if collection? || pluralized? }
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
                protected :local_mountings
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                def assert_key_uniqueness!(values)
         | 
| 136 | 
            +
                  keys = values.map{ |v| v['key'] }.compact
         | 
| 137 | 
            +
                  keys == keys.uniq or
         | 
| 138 | 
            +
                    fail NonUniqKeysError, "All keys in collection '#{name}' should be uniq, but were not"
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
                private :assert_key_uniqueness!
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                def assert_collection!(data)
         | 
| 143 | 
            +
                  unless data.respond_to?(:each)
         | 
| 144 | 
            +
                    fail ArgumentError, "Cannot write to '#{name}': argument is not a collection"
         | 
| 145 | 
            +
                  end
         | 
| 146 | 
            +
                end
         | 
| 147 | 
            +
                private :assert_collection!
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                def collection
         | 
| 150 | 
            +
                  return nil unless collection?
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  @collection ||= target.each.with_index.map do |item, index|
         | 
| 153 | 
            +
                    clone.tap do |mapper|
         | 
| 154 | 
            +
                      mapper.reset_locals!
         | 
| 155 | 
            +
                      mapper.set_target! item
         | 
| 156 | 
            +
                      mapper.item_index = index
         | 
| 157 | 
            +
                    end
         | 
| 158 | 
            +
                  end
         | 
| 159 | 
            +
                end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                def prefix
         | 
| 162 | 
            +
                  return super if mounter.nil?
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  [mounter.prefix, item_name].compact.join(?.).presence
         | 
| 165 | 
            +
                end
         | 
| 166 | 
            +
                protected :prefix
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                def item_name
         | 
| 169 | 
            +
                  "#{name}.#{item_index}" if item_index.present?
         | 
| 170 | 
            +
                end
         | 
| 171 | 
            +
                protected :item_name
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                def as_inner_mountings
         | 
| 174 | 
            +
                  if collection?
         | 
| 175 | 
            +
                    ensure_target!
         | 
| 176 | 
            +
                    collection.map{ |item| item.as_inner_mountings }
         | 
| 177 | 
            +
                  else
         | 
| 178 | 
            +
                    super
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
                end
         | 
| 181 | 
            +
                protected :as_inner_mountings
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                def reset_locals!
         | 
| 184 | 
            +
                  @_local_mappings = nil
         | 
| 185 | 
            +
                  @_local_mountings = nil
         | 
| 186 | 
            +
                end
         | 
| 187 | 
            +
                protected :reset_locals!
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                def collection?
         | 
| 190 | 
            +
                  options[:collection] && item_index.nil?
         | 
| 191 | 
            +
                end
         | 
| 192 | 
            +
              end
         | 
| 193 | 
            +
            end
         | 
| @@ -4,6 +4,9 @@ module Flatter | |
| 4 4 | 
             
                prepend Flatter::Mapper::Mounting::FactoryMethods
         | 
| 5 5 | 
             
                prepend Flatter::Mapper::Traits::FactoryMethods
         | 
| 6 6 | 
             
                prepend Flatter::Mapper::Options::FactoryMethods
         | 
| 7 | 
            +
                prepend Flatter::Mapper::Collection::FactoryMethods
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                NoTargetError = Class.new(RuntimeError)
         | 
| 7 10 |  | 
| 8 11 | 
             
                attr_reader :name, :options
         | 
| 9 12 |  | 
| @@ -16,19 +19,36 @@ module Flatter | |
| 16 19 | 
             
                end
         | 
| 17 20 |  | 
| 18 21 | 
             
                def mapper_class_name
         | 
| 19 | 
            -
                  options[:mapper_class_name] ||  | 
| 22 | 
            +
                  options[:mapper_class_name] || modulize(default_mapper_class_name)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def default_mapper_class_name
         | 
| 26 | 
            +
                  "#{name.camelize}Mapper"
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def create(*)
         | 
| 30 | 
            +
                  mapper_class.new.tap{ |mapper| mapper.factory = self }
         | 
| 20 31 | 
             
                end
         | 
| 21 32 |  | 
| 22 | 
            -
                def  | 
| 23 | 
            -
                   | 
| 33 | 
            +
                def modulize(class_name)
         | 
| 34 | 
            +
                  if i = options[:mounter_name].rindex('::')
         | 
| 35 | 
            +
                    "#{options[:mounter_name][0...i]}::#{class_name}"
         | 
| 36 | 
            +
                  else
         | 
| 37 | 
            +
                    class_name
         | 
| 38 | 
            +
                  end
         | 
| 24 39 | 
             
                end
         | 
| 40 | 
            +
                private :modulize
         | 
| 25 41 |  | 
| 26 42 | 
             
                def fetch_target_from(mapper)
         | 
| 27 43 | 
             
                  default_target_from(mapper)
         | 
| 28 44 | 
             
                end
         | 
| 29 45 |  | 
| 30 46 | 
             
                def default_target_from(mapper)
         | 
| 31 | 
            -
                   | 
| 47 | 
            +
                  if mapper.target.respond_to?(name)
         | 
| 48 | 
            +
                    mapper.target.public_send(name)
         | 
| 49 | 
            +
                  else
         | 
| 50 | 
            +
                    fail NoTargetError, "Unable to implicitly fetch target for '#{name}' from #{mapper}"
         | 
| 51 | 
            +
                  end
         | 
| 32 52 | 
             
                end
         | 
| 33 53 | 
             
                private :default_target_from
         | 
| 34 54 | 
             
              end
         | 
| @@ -37,15 +37,13 @@ module Flatter | |
| 37 37 | 
             
                end
         | 
| 38 38 |  | 
| 39 39 | 
             
                def write(params)
         | 
| 40 | 
            -
                  params = params.with_indifferent_access
         | 
| 41 40 | 
             
                  local_mappings.each{ |mapping| mapping.write_from_params(params) }
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                  params
         | 
| 44 41 | 
             
                end
         | 
| 45 42 |  | 
| 46 43 | 
             
                def local_mappings
         | 
| 47 44 | 
             
                  @_local_mappings ||= self.class.mappings.values.map{ |factory| factory.create(self) }
         | 
| 48 45 | 
             
                end
         | 
| 46 | 
            +
                protected :local_mappings
         | 
| 49 47 |  | 
| 50 48 | 
             
                def mappings
         | 
| 51 49 | 
             
                  local_mappings.each_with_object({}) do |mapping, res|
         | 
| @@ -54,7 +52,7 @@ module Flatter | |
| 54 52 | 
             
                end
         | 
| 55 53 |  | 
| 56 54 | 
             
                def mapping_names
         | 
| 57 | 
            -
                   | 
| 55 | 
            +
                  local_mappings.map(&:name)
         | 
| 58 56 | 
             
                end
         | 
| 59 57 |  | 
| 60 58 | 
             
                def writable_mapping_names
         | 
| @@ -11,9 +11,14 @@ module Flatter | |
| 11 11 | 
             
                  end
         | 
| 12 12 | 
             
                end
         | 
| 13 13 |  | 
| 14 | 
            +
                included do
         | 
| 15 | 
            +
                  class_attribute :label
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 14 18 | 
             
                module ClassMethods
         | 
| 15 | 
            -
                  def mount(name,  | 
| 16 | 
            -
                     | 
| 19 | 
            +
                  def mount(name, **opts)
         | 
| 20 | 
            +
                    factory_options = opts.reverse_merge(mounter_name: self.name || label)
         | 
| 21 | 
            +
                    mountings[name.to_s] = Flatter::Mapper::Factory.new(name, **factory_options)
         | 
| 17 22 | 
             
                  end
         | 
| 18 23 |  | 
| 19 24 | 
             
                  def mountings
         | 
| @@ -35,26 +40,30 @@ module Flatter | |
| 35 40 | 
             
                  super.tap do |mappings|
         | 
| 36 41 | 
             
                    inner_mountings.each do |mounting|
         | 
| 37 42 | 
             
                      mounting.local_mappings.each do |mapping|
         | 
| 38 | 
            -
                        mappings | 
| 43 | 
            +
                        mappings.merge!(mapping.name => mapping, &merging_proc)
         | 
| 39 44 | 
             
                      end
         | 
| 40 45 | 
             
                    end
         | 
| 41 46 | 
             
                  end
         | 
| 42 47 | 
             
                end
         | 
| 43 48 |  | 
| 49 | 
            +
                def mapping_names
         | 
| 50 | 
            +
                  super + local_mountings.map(&:mapping_names).flatten
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 44 53 | 
             
                def read
         | 
| 45 | 
            -
                   | 
| 54 | 
            +
                  local_mountings.map(&:read).inject(super, :merge)
         | 
| 46 55 | 
             
                end
         | 
| 47 56 |  | 
| 48 57 | 
             
                def write(params)
         | 
| 49 | 
            -
                  super | 
| 50 | 
            -
             | 
| 51 | 
            -
                   | 
| 58 | 
            +
                  super
         | 
| 59 | 
            +
                  local_mountings.each{ |mapper| mapper.write(params) }
         | 
| 60 | 
            +
                  @_inner_mountings = nil
         | 
| 52 61 | 
             
                end
         | 
| 53 62 |  | 
| 54 63 | 
             
                def local_mountings
         | 
| 55 64 | 
             
                  class_mountings_for(self.class)
         | 
| 56 65 | 
             
                end
         | 
| 57 | 
            -
                 | 
| 66 | 
            +
                protected :local_mountings
         | 
| 58 67 |  | 
| 59 68 | 
             
                def class_mountings_for(klass)
         | 
| 60 69 | 
             
                  class_mountings(klass).map{ |factory| factory.create(self) }
         | 
| @@ -67,18 +76,33 @@ module Flatter | |
| 67 76 | 
             
                private :class_mountings
         | 
| 68 77 |  | 
| 69 78 | 
             
                def mountings
         | 
| 70 | 
            -
                  @mountings ||= inner_mountings. | 
| 71 | 
            -
                    res | 
| 79 | 
            +
                  @mountings ||= inner_mountings.inject({}) do |res, mapper|
         | 
| 80 | 
            +
                    res.merge(mapper.full_name => mapper, &merging_proc)
         | 
| 72 81 | 
             
                  end
         | 
| 73 82 | 
             
                end
         | 
| 74 83 |  | 
| 75 | 
            -
                def  | 
| 76 | 
            -
                   | 
| 84 | 
            +
                def mounting_names
         | 
| 85 | 
            +
                  local_mounting_names + local_mountings.map(&:mounting_names).flatten
         | 
| 77 86 | 
             
                end
         | 
| 78 87 |  | 
| 88 | 
            +
                def local_mounting_names
         | 
| 89 | 
            +
                  local_mountings.map(&:name)
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
                private :local_mounting_names
         | 
| 92 | 
            +
             | 
| 79 93 | 
             
                def inner_mountings
         | 
| 80 | 
            -
                  @_inner_mountings ||= local_mountings.map{ |mount|  | 
| 94 | 
            +
                  @_inner_mountings ||= local_mountings.map{ |mount| mount.as_inner_mountings }.flatten
         | 
| 81 95 | 
             
                end
         | 
| 82 96 | 
             
                protected :inner_mountings
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                def as_inner_mountings
         | 
| 99 | 
            +
                  [self, inner_mountings]
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
                protected :as_inner_mountings
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def merging_proc
         | 
| 104 | 
            +
                  proc { |_, old, new| Array(old).push(new) }
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
                private :merging_proc
         | 
| 83 107 | 
             
              end
         | 
| 84 108 | 
             
            end
         | 
| @@ -61,12 +61,21 @@ module Flatter | |
| 61 61 | 
             
                private :self_mountings
         | 
| 62 62 |  | 
| 63 63 | 
             
                def consolidate_errors!
         | 
| 64 | 
            -
                  root_mountings. | 
| 65 | 
            -
                     | 
| 64 | 
            +
                  root_mountings.each do |mounting|
         | 
| 65 | 
            +
                    prefix = mounting.prefix
         | 
| 66 | 
            +
                    mounting.errors.to_hash.each do |name, errs|
         | 
| 67 | 
            +
                      error_key = [prefix, name].compact.join('.')
         | 
| 68 | 
            +
                      errors.messages.merge!(error_key.to_sym => errs){ |key, old, new| old + new }
         | 
| 69 | 
            +
                    end
         | 
| 66 70 | 
             
                  end
         | 
| 67 71 | 
             
                end
         | 
| 68 72 | 
             
                private :consolidate_errors!
         | 
| 69 73 |  | 
| 74 | 
            +
                def prefix
         | 
| 75 | 
            +
                  nil
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
                protected :prefix
         | 
| 78 | 
            +
             | 
| 70 79 | 
             
                def errors
         | 
| 71 80 | 
             
                  trait? ? mounter.errors : super
         | 
| 72 81 | 
             
                end
         | 
| @@ -1,7 +1,13 @@ | |
| 1 1 | 
             
            module Flatter
         | 
| 2 2 | 
             
              module Mapper::Target
         | 
| 3 | 
            +
                extend ActiveSupport::Concern
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
                NoTargetError = Class.new(ArgumentError)
         | 
| 4 6 |  | 
| 7 | 
            +
                included do
         | 
| 8 | 
            +
                  mapper_options << :target_class_name
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 5 11 | 
             
                module FactoryMethods
         | 
| 6 12 | 
             
                  def fetch_target_from(mapper)
         | 
| 7 13 | 
             
                    return super unless options.key?(:target)
         | 
| @@ -19,20 +25,60 @@ module Flatter | |
| 19 25 | 
             
                  end
         | 
| 20 26 | 
             
                end
         | 
| 21 27 |  | 
| 22 | 
            -
                 | 
| 23 | 
            -
             | 
| 24 | 
            -
                def initialize(target, *)
         | 
| 25 | 
            -
                  unless target.present?
         | 
| 26 | 
            -
                    fail NoTargetError, "Target object is required to initialize #{self.class.name}"
         | 
| 27 | 
            -
                  end
         | 
| 28 | 
            +
                attr_accessor :factory
         | 
| 28 29 |  | 
| 30 | 
            +
                def initialize(target = nil, *)
         | 
| 29 31 | 
             
                  super
         | 
| 32 | 
            +
                  set_target!(target) if target.present?
         | 
| 33 | 
            +
                end
         | 
| 30 34 |  | 
| 31 | 
            -
             | 
| 35 | 
            +
                def target
         | 
| 36 | 
            +
                  ensure_target!
         | 
| 37 | 
            +
                  @target
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def ensure_target!
         | 
| 41 | 
            +
                  initialize_target unless target_initialized?
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
                protected :ensure_target!
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def initialize_target
         | 
| 46 | 
            +
                  return set_target!(mounter.target) if trait?
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  _mounter = mounter.trait? ? mounter.mounter : mounter
         | 
| 49 | 
            +
                  set_target!(factory.fetch_target_from(_mounter))
         | 
| 32 50 | 
             
                end
         | 
| 51 | 
            +
                private :initialize_target
         | 
| 33 52 |  | 
| 34 53 | 
             
                def set_target(target)
         | 
| 54 | 
            +
                  if trait?
         | 
| 55 | 
            +
                    mounter.set_target!(target)
         | 
| 56 | 
            +
                  else
         | 
| 57 | 
            +
                    set_target!(target)
         | 
| 58 | 
            +
                    trait_mountings.each{ |trait| trait.set_target!(target) }
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def set_target!(target)
         | 
| 63 | 
            +
                  fail NoTargetError, "Cannot set nil target for #{self.class.name}" if target.nil?
         | 
| 64 | 
            +
                  @_target_initialized = true
         | 
| 35 65 | 
             
                  @target = target
         | 
| 36 66 | 
             
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def target_initialized?
         | 
| 69 | 
            +
                  !!@_target_initialized
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def target_class
         | 
| 73 | 
            +
                  target_class_name.constantize
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                def target_class_name
         | 
| 77 | 
            +
                  options[:target_class_name] || default_target_class_name
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def default_target_class_name
         | 
| 81 | 
            +
                  self.class.name.sub 'Mapper', ''
         | 
| 82 | 
            +
                end
         | 
| 37 83 | 
             
              end
         | 
| 38 84 | 
             
            end
         | 
| @@ -20,10 +20,6 @@ module Flatter | |
| 20 20 | 
             
                      mounting.extend_with(extension) if extension.present?
         | 
| 21 21 | 
             
                    end
         | 
| 22 22 | 
             
                  end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                  def fetch_target_from(mapper)
         | 
| 25 | 
            -
                    trait? ? mapper.target : super
         | 
| 26 | 
            -
                  end
         | 
| 27 23 | 
             
                end
         | 
| 28 24 |  | 
| 29 25 | 
             
                module ClassMethods
         | 
| @@ -31,9 +27,11 @@ module Flatter | |
| 31 27 | 
             
                    super.tap{ |f| f.extension = block }
         | 
| 32 28 | 
             
                  end
         | 
| 33 29 |  | 
| 34 | 
            -
                  def trait(name, &block)
         | 
| 30 | 
            +
                  def trait(name, label = nil, &block)
         | 
| 35 31 | 
             
                    trait_name   = "#{name}_trait"
         | 
| 36 | 
            -
                    mapper_class = Class.new(Flatter::Mapper | 
| 32 | 
            +
                    mapper_class = Class.new(Flatter::Mapper)
         | 
| 33 | 
            +
                    mapper_class.label = self.name || label
         | 
| 34 | 
            +
                    mapper_class.class_eval(&block)
         | 
| 37 35 |  | 
| 38 36 | 
             
                    if self.name.present?
         | 
| 39 37 | 
             
                      mapper_class_name = trait_name.camelize
         | 
| @@ -46,7 +44,7 @@ module Flatter | |
| 46 44 | 
             
                  end
         | 
| 47 45 | 
             
                end
         | 
| 48 46 |  | 
| 49 | 
            -
                def initialize( | 
| 47 | 
            +
                def initialize(_, *traits, **, &block)
         | 
| 50 48 | 
             
                  super
         | 
| 51 49 |  | 
| 52 50 | 
             
                  set_traits(traits)
         | 
| @@ -54,22 +52,8 @@ module Flatter | |
| 54 52 | 
             
                end
         | 
| 55 53 |  | 
| 56 54 | 
             
                def extend_with(extension)
         | 
| 57 | 
            -
                  singleton_class.trait :extension, &extension
         | 
| 58 | 
            -
                end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                def set_target(target)
         | 
| 61 | 
            -
                  if trait?
         | 
| 62 | 
            -
                    mounter.set_target(target)
         | 
| 63 | 
            -
                  else
         | 
| 64 | 
            -
                    super
         | 
| 65 | 
            -
                    trait_mountings.each{ |trait| trait.set_target!(target) }
         | 
| 66 | 
            -
                  end
         | 
| 67 | 
            -
                end
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                def set_target!(target)
         | 
| 70 | 
            -
                  @target = target
         | 
| 55 | 
            +
                  singleton_class.trait :extension, self.class.name, &extension
         | 
| 71 56 | 
             
                end
         | 
| 72 | 
            -
                protected :set_target!
         | 
| 73 57 |  | 
| 74 58 | 
             
                def full_name
         | 
| 75 59 | 
             
                  if name == 'extension_trait'
         | 
| @@ -80,7 +64,7 @@ module Flatter | |
| 80 64 | 
             
                end
         | 
| 81 65 |  | 
| 82 66 | 
             
                def local_mountings
         | 
| 83 | 
            -
                  @ | 
| 67 | 
            +
                  @_local_mountings ||= class_mountings_for(singleton_class) + super
         | 
| 84 68 | 
             
                end
         | 
| 85 69 | 
             
                private :local_mountings
         | 
| 86 70 |  | 
| @@ -126,6 +110,11 @@ module Flatter | |
| 126 110 | 
             
                  @trait = true
         | 
| 127 111 | 
             
                end
         | 
| 128 112 |  | 
| 113 | 
            +
                def local_mounting_names
         | 
| 114 | 
            +
                  super.reject{ |name| trait_mountings.any?{ |mount| mount.name == name } }
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
                private :local_mounting_names
         | 
| 117 | 
            +
             | 
| 129 118 | 
             
                def trait_mountings
         | 
| 130 119 | 
             
                  @_trait_mountings ||= local_mountings.select(&:trait?)
         | 
| 131 120 | 
             
                end
         | 
    
        data/lib/flatter/mapper.rb
    CHANGED
    
    | @@ -4,23 +4,27 @@ module Flatter | |
| 4 4 |  | 
| 5 5 | 
             
                autoload :Factory
         | 
| 6 6 | 
             
                autoload :Options
         | 
| 7 | 
            -
                autoload :Target
         | 
| 8 7 | 
             
                autoload :Mapping
         | 
| 9 8 | 
             
                autoload :Mounting
         | 
| 10 9 | 
             
                autoload :Traits
         | 
| 10 | 
            +
                autoload :Target
         | 
| 11 11 | 
             
                autoload :AttributeMethods
         | 
| 12 12 | 
             
                autoload :Persistence
         | 
| 13 13 | 
             
                autoload :ModelName
         | 
| 14 | 
            +
                autoload :Collection
         | 
| 15 | 
            +
                autoload :WriteWithIndifferentAccess
         | 
| 14 16 |  | 
| 15 17 | 
             
                include Options
         | 
| 16 | 
            -
                include Target
         | 
| 17 18 | 
             
                include Mapping
         | 
| 18 19 | 
             
                include Mounting
         | 
| 19 20 | 
             
                include Traits
         | 
| 21 | 
            +
                include Target
         | 
| 20 22 | 
             
                include AttributeMethods
         | 
| 21 23 | 
             
                include ActiveModel::Validations
         | 
| 22 24 | 
             
                include Persistence
         | 
| 23 25 | 
             
                prepend ModelName
         | 
| 26 | 
            +
                prepend Collection
         | 
| 27 | 
            +
                prepend WriteWithIndifferentAccess
         | 
| 24 28 |  | 
| 25 29 | 
             
                def self.inherited(subclass)
         | 
| 26 30 | 
             
                  subclass.mappings  = mappings.dup
         | 
    
        data/lib/flatter/version.rb
    CHANGED