class Praxis::Mapper::Model

Constants

InspectedFields

Attributes

_identities[RW]
associations[R]
config[R]
contexts[R]
serialized_fields[R]
_query[RW]
_resource[RW]
identity_map[RW]
new_record[RW]

Public Class Methods

_finalize!() click to toggle source

Internal finalize! logic

Calls superclass method
# File lib/praxis-mapper/model.rb, line 30
def self._finalize!
  self.define_data_accessors *self.identities.flatten

  self.associations.each do |name,config|
    self.associations[name] = config.to_hash
  end

  self.define_associations

  super
end
all(condition={}) click to toggle source

Looks up in the identity map first.

@param condition [Hash] ? @return [Array<Model>] all matching records

# File lib/praxis-mapper/model.rb, line 378
def self.all(condition={})
  IdentityMap.current.all(self, condition)
end
array_to_many(name, &block) click to toggle source

Define array_to_many (aka: belongs_to where the key attribute is an array)

# File lib/praxis-mapper/model.rb, line 138
def self.array_to_many(name, &block)
  self.associations[name] = ConfigHash.from(type: :array_to_many, &block)
end
belongs_to(name=nil, belongs_to_options={}) click to toggle source

Implements Praxis::Mapper DSL directive 'belongs_to'. If name and belongs_to_options are given, upserts the association. If only name is given, gets the named association. Else, returns all configured associations.

@param name [String] name of association to set or get @param belongs_to_options [Hash] new association options @option :model [Model] the associated model @option :fk [String] associated field name @option :source_key [String] local field name @option :type [Symbol] type of mapping, :scalar or :array @return [Array] all configured associations @example

belongs_to :parents, :model => ParentModel,
    :source_key => :parent_ids,
    :fk => :id,
    :type => :array
# File lib/praxis-mapper/model.rb, line 101
def self.belongs_to(name=nil, belongs_to_options={})
  if !belongs_to_options.empty?
    warn "DEPRECATION: `#{self}.belongs_to` is deprecated. Use `many_to_one` or `array_to_many` instead."

    opts = {:fk => :id}.merge(belongs_to_options)

    opts[:key] = opts.delete(:source_key)
    opts[:primary_key] = opts.delete(:fk) if opts.has_key?(:fk)

    if (opts.delete(:type) == :array)
      opts[:type] = :array_to_many
    else
      opts[:type] = :many_to_one
    end

    self.associations[name] = opts


    define_belongs_to(name, opts)
  else
    raise "Calling Model.belongs to fetch association information is no longer supported. Use Model.associations instead."
  end
end
context(name, &block) click to toggle source
# File lib/praxis-mapper/model.rb, line 214
def self.context(name, &block)
  default = Hash.new do |hash, key|
    hash[key] = Array.new
  end
  @contexts[name] = ConfigHash.from(default, &block).to_hash
end
define_array_to_many(name, association) click to toggle source
# File lib/praxis-mapper/model.rb, line 298
    def self.define_array_to_many(name, association)
      model = association[:model]
      primary_key = association.fetch(:primary_key, :id)
      key = association.fetch(:key)
      
      module_eval <<-RUBY, __FILE__, __LINE__ + 1
       def #{name}
          return nil if #{key}.nil?
          @__#{name} ||= self.identity_map.all(#{model.name},#{primary_key.inspect} => #{key})
        end
      RUBY

    end
define_association(name, association) click to toggle source
# File lib/praxis-mapper/model.rb, line 245
def self.define_association(name, association)
  case association[:type]
  when :many_to_one
    self.define_many_to_one(name, association)
  when :array_to_many
    self.define_array_to_many(name, association)
  when :one_to_many
    self.define_one_to_many(name, association)
  when :many_to_array
    self.define_many_to_array(name, association)
  end
end
define_associations() click to toggle source
# File lib/praxis-mapper/model.rb, line 239
def self.define_associations
  self.associations.each do |name, association|
    self.define_association(name,association)
  end
end
define_belongs_to(name, opts) click to toggle source

The belongs_to association creates a one-to-one match with another model. In database terms, this association says that this class contains the foreign key.

@param name [Symbol] name of association; typically the same as associated model name @param opts [Hash] association options @option :model [Model] the associated model @option :fk [String] associated field name @option :source_key [String] local field name @option :type [Symbol] type of mapping, :scalar or :array

@example

define_belongs_to(:customer, {:model => Customer, :fk => :id, :source_key => :customer_id, :type => scalar})

@see guides.rubyonrails.org/v2.3.11/association_basics.html#belongs-to-association-reference

# File lib/praxis-mapper/model.rb, line 352
def self.define_belongs_to(name, opts)
  model = opts.fetch(:model)
  type = opts.fetch(:type, :many_to_one) # :scalar has no meaning other than it's not an array

  case opts.fetch(:type, :many_to_one)      
  when :many_to_one
    return self.define_many_to_one(name, opts)
  when :array_to_many
    return self.define_array_to_many(name, opts)
  end
end
define_data_accessor(name) click to toggle source
# File lib/praxis-mapper/model.rb, line 229
    def self.define_data_accessor(name)
      module_eval <<-RUBY, __FILE__, __LINE__ + 1
        def #{name}
          @__#{name} ||= @data.fetch(#{name.inspect}) do
            raise "field #{name.inspect} not loaded for #{self.inspect}."
          end.freeze
        end
      RUBY
    end
define_data_accessors(*names) click to toggle source
# File lib/praxis-mapper/model.rb, line 222
def self.define_data_accessors(*names)
  names.each do |name|
    self.define_data_accessor(name)
  end
end
define_many_to_array(name, association) click to toggle source
# File lib/praxis-mapper/model.rb, line 313
    def self.define_many_to_array(name, association)
      model = association[:model]
      primary_key = association.fetch(:primary_key, :id)
      key_name = association.fetch(:key)

      if primary_key.kind_of?(Array)
        key = "["
        key += primary_key.collect { |k| "self.#{k}" }.join(", ")
        key += "]"
      else
        key = "self.#{primary_key}"
      end

      module_eval <<-RUBY, __FILE__, __LINE__ + 1
        def #{name}
          key = #{key}
          return nil if key.nil?
          @__#{name} ||= self.identity_map.all(#{model.name}).
            select { |record| record.#{key_name}.include? key }
        end
      RUBY
    end
define_many_to_one(name, association) click to toggle source
# File lib/praxis-mapper/model.rb, line 276
    def self.define_many_to_one(name, association)
      model = association[:model]
      primary_key = association.fetch(:primary_key, :id)

      if association[:key].kind_of?(Array)
        key = "["
        key += association[:key].collect { |k| "self.#{k}" }.join(", ")
        key += "]"
      else
        key = "self.#{association[:key]}"
      end

      module_eval <<-RUBY, __FILE__, __LINE__ + 1
        def #{name}
          return nil if #{key}.nil?
          @__#{name} ||= self.identity_map.get(#{model.name},#{primary_key.inspect} => #{key})
        end
      RUBY

    end
define_one_to_many(name, association) click to toggle source

has_many

# File lib/praxis-mapper/model.rb, line 259
def self.define_one_to_many(name, association)
  model = association[:model]
  primary_key = association.fetch(:primary_key, :id)
  
  if primary_key.kind_of?(Array)
    define_method(name) do 
      pk = primary_key.collect { |k| self.send(k) }
      self.identity_map.all(model,association[:key] => [pk])
    end
  else
    define_method(name) do
      pk = self.send(primary_key)
      self.identity_map.all(model,association[:key] => [pk])
    end
  end
end
define_serialized_accessor(name, serializer, **opts) click to toggle source
# File lib/praxis-mapper/model.rb, line 200
def self.define_serialized_accessor(name, serializer, **opts)
  define_method(name) do
    @deserialized_data[name] ||= if (value = @data.fetch(name))
      serializer.load(value)
    else
      opts[:default]
    end
  end

  define_method("_raw_#{name}".to_sym) do
    @data.fetch name
  end
end
excluded_scopes(*scopes) click to toggle source

Implements Praxis::Mapper DSL directive 'excluded_scopes'. Gets or sets the excluded scopes for this model. Exclusion means that the named condition cannot be applied.

@param *scopes [Array] list of scopes to exclude @return [Array] configured list of scopes @example excluded_scopes :account, :deleted_at

# File lib/praxis-mapper/model.rb, line 49
def self.excluded_scopes(*scopes)
  if scopes.any?
    self.config[:excluded_scopes] = scopes
  else
    self.config.fetch(:excluded_scopes)
  end
end
get(condition) click to toggle source

Looks up in the identity map first.

@param condition ? @return [Model] matching record

# File lib/praxis-mapper/model.rb, line 369
def self.get(condition)
  IdentityMap.current.get(self, condition)
end
identities(*names) click to toggle source

Implements Praxis::Mapper DSL directive 'identities'. Gets or sets list of identity fields.

@param *names [Array] list of identity fields to set @return [Array] configured list of identity fields @example identities :id, :type

# File lib/praxis-mapper/model.rb, line 163
def self.identities(*names)
  if names.any?
    self.config[:identities] = names
    @_identities = names
  else
    self.config.fetch(:identities)
  end
end
identity(name) click to toggle source

Adds given identity to the list of model identities. May be an array for composite keys.

# File lib/praxis-mapper/model.rb, line 150
def self.identity(name)
  @_identities ||= Array.new
  @_identities << name
  self.config[:identities] << name
end
inherited(klass) click to toggle source
Calls superclass method
# File lib/praxis-mapper/model.rb, line 15
def self.inherited(klass)
  super

  klass.instance_eval do
    @config = {
      excluded_scopes: [],
      identities: []
    }
    @associations = {}
    @serialized_fields = {}
    @contexts = Hash.new
  end
end
json(name, opts={}) click to toggle source

Implements Praxis::Mapper DSL directive 'json'. This will perform JSON.load on serialized data.

@param name [String] name of field that is serialized as JSON @param opts [Hash] @options :default [String] default value?

@example yaml :parent_ids, :default => []

# File lib/praxis-mapper/model.rb, line 195
def self.json(name, opts={})
  @serialized_fields[name] = :json
  define_serialized_accessor(name, JSON, opts)
end
many_to_array(name, &block) click to toggle source

Define many_to_array (aka: has_many where the key attribute is an array)

# File lib/praxis-mapper/model.rb, line 143
def self.many_to_array(name, &block)
  self.associations[name] = ConfigHash.from(type: :many_to_array, &block)
end
many_to_one(name, &block) click to toggle source

Define many_to_one (aka: belongs_to)

# File lib/praxis-mapper/model.rb, line 133
def self.many_to_one(name, &block)
  self.associations[name] = ConfigHash.from(type: :many_to_one, &block)
end
new(data) click to toggle source
# File lib/praxis-mapper/model.rb, line 383
def initialize(data)
  @data = data
  @deserialized_data = {}
  @query = nil
end
one_to_many(name, &block) click to toggle source

Define one_to_many (aka: has_many)

# File lib/praxis-mapper/model.rb, line 127
def self.one_to_many(name, &block)
  self.associations[name] = ConfigHash.from(type: :one_to_many, &block)
end
repository_name(name=nil) click to toggle source

Gets or sets the repository for this model.

@param name [Symbol] repository name @return [Symbol] repository name or :default

# File lib/praxis-mapper/model.rb, line 61
def self.repository_name(name=nil)
  if name
    self.config[:repository_name] = name
  else
    self.config.fetch(:repository_name, :default)
  end
end
table_name(name=nil) click to toggle source

Implements Praxis::Mapper DSL directive 'table_name'. Gets or sets the SQL-like table name. Can also be thought of as a namespace in the repository.

@param name [Symbol] table name @return [Symbol] table name or nil @example table_name 'json_array_model'

# File lib/praxis-mapper/model.rb, line 76
def self.table_name(name=nil)
  if name
    self.config[:table_name] = name
  else
    self.config.fetch(:table_name, nil)
  end
end
yaml(name, opts={}) click to toggle source

Implements Praxis::Mapper DSL directive 'yaml'. This will perform YAML.load on serialized data.

@param name [String] name of field that is serialized as YAML @param opts [Hash] @options :default [String] default value?

@example yaml :parent_ids, :default => []

# File lib/praxis-mapper/model.rb, line 181
def self.yaml(name, opts={})
  @serialized_fields[name] = :yaml
  define_serialized_accessor(name, YAML, opts)
end

Public Instance Methods

_data() click to toggle source
# File lib/praxis-mapper/model.rb, line 424
def _data
  @data
end
identities() click to toggle source
# File lib/praxis-mapper/model.rb, line 413
def identities
  self.class._identities.each_with_object(Hash.new) do |identity, hash|
    case identity
    when Symbol
      hash[identity] = @data[identity].freeze
    else
      hash[identity] = @data.values_at(*identity).collect(&:freeze)
    end
  end
end
inspect() click to toggle source
# File lib/praxis-mapper/model.rb, line 390
def inspect
"#<#{self.class}:0x#{object_id.to_s(16)} #{
      instance_variables.select{|v| InspectedFields.include? v}.map {|var|
        "#{var}: #{instance_variable_get(var).inspect}"
      }.join("#{'  '}")
    }#{'  '}>"
end
method_missing(name, *args) click to toggle source
Calls superclass method
# File lib/praxis-mapper/model.rb, line 403
def method_missing(name, *args)
  if @data.has_key? name
    self.class.define_data_accessor(name)
    self.send(name)
  else
    super
  end
end
Also aliased as: original_method_missing
original_method_missing(name, *args)
Alias for: method_missing
respond_to_missing?(name, *) click to toggle source
Calls superclass method
# File lib/praxis-mapper/model.rb, line 398
def respond_to_missing?(name, *)
  @data.key?(name) || super
end
save!() click to toggle source
# File lib/praxis-mapper/support/factory_bot.rb, line 7
def save!
  @new_record = true
  unless Praxis::Mapper::IdentityMap.current.add_records([self]).include? self
    raise "Conflict trying to save record with type: #{self.class} and data:\n#{@data.pretty_inspect}"
  end
end
set_association(name, value) click to toggle source
# File lib/praxis-mapper/support/factory_bot.rb, line 50
def set_association(name, value)
  spec = self.class.associations.fetch(name)

  case spec[:type]
  when :one_to_many
    raise "can not set one_to_many associations to nil" if value.nil?
    primary_key = @data[spec[:primary_key]]
    setter_name = "#{spec[:key]}="
    Array(value).each { |item| item.send(setter_name, primary_key) }
  when :many_to_one
    primary_key = value && value.send(spec[:primary_key])
    @data[spec[:key]] = primary_key
  else
    raise "can not handle associations of type #{spec[:type]}"
  end

end
set_serialized_field(name,value) click to toggle source
# File lib/praxis-mapper/support/factory_bot.rb, line 37
def set_serialized_field(name,value)
  @deserialized_data[name] = value

  case self.class.serialized_fields[name]
  when :json
    @data[name] = JSON.dump(value)
  when :yaml
    @data[name] = YAML.dump(value)
  else
    @data[name] = value # dunno
  end
end