Class: Attributor::DSLCompiler

Inherits:
Object
  • Object
show all
Includes:
Attributor
Defined in:
lib/attributor/dsl_compiler.rb

Overview

The reference option for an attribute is passed if a block is given

Direct Known Subclasses

HashDSLCompiler

Constant Summary

Constants included from Attributor

DEFAULT_ROOT_CONTEXT, MODULE_PREFIX, MODULE_PREFIX_REGEX, ROOT_PREFIX, SEPARATOR, VERSION

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Attributor

#_get_attr, errorize_value, find_type, humanize_context, recursive_to_h, resolve_type, type_name

Constructor Details

#initialize(target, **options) ⇒ DSLCompiler

Returns a new instance of DSLCompiler.



16
17
18
19
# File 'lib/attributor/dsl_compiler.rb', line 16

def initialize(target, **options)
  @target = target
  @options = options
end

Instance Attribute Details

#optionsObject

Returns the value of attribute options.



13
14
15
# File 'lib/attributor/dsl_compiler.rb', line 13

def options
  @options
end

#targetObject

Returns the value of attribute target.



13
14
15
# File 'lib/attributor/dsl_compiler.rb', line 13

def target
  @target
end

Instance Method Details

#add_reference_to_block(name, opts) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/attributor/dsl_compiler.rb', line 156

def add_reference_to_block(name, opts)
  base_reference  = options[:reference]
  if opts[:reference] # Direct reference specified in the attribute, just pass it to the block
    {reference: opts[:reference]}
  elsif( base_reference && base_reference.respond_to?(:attributes) && base_reference.attributes.key?(name))
    selected_type = base_reference.attributes[name].type
    selected_type = selected_type.member_attribute.type if selected_type < Attributor::Collection
    {reference: selected_type}
  else
    {}
  end
end

#attribute(name, attr_type = nil, **opts, &block) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/attributor/dsl_compiler.rb', line 35

def attribute(name, attr_type = nil, **opts, &block)
  raise AttributorException, "Attribute names must be symbols, got: #{name.inspect}" unless name.is_a? ::Symbol
  if opts[:reference]
    raise AttributorException, ":reference option can only be used when defining blocks"  unless block_given? 
    if opts[:reference] < Attributor::Collection
      err = ":reference option cannot be a collection. It must be a concrete Struct-like type containing the attribute #{name} you are defining.\n"
      location_file, location_line = block.source_location               
      err += "The place where you are trying to define the attribute is here:\n#{location_file} line #{location_line}\n#{block.source}\n"
      raise AttributorException, err
    end
  end
  target.attributes[name] = define(name, attr_type, **opts, &block)
end

#attributesObject



27
28
29
30
31
32
33
# File 'lib/attributor/dsl_compiler.rb', line 27

def attributes
  if target.respond_to?(:attributes)
    target.attributes
  else
    target.keys
  end
end

#define(name, type, opts, &block) ⇒ Object #define(name, opts, &block) ⇒ Object

Creates an Attributor:Attribute with given definition.

Overloads:

  • #define(name, type, opts, &block) ⇒ Object

    With an explicit type.

    Examples:

    attribute :email, String, example: Randgen.email

    Parameters:

    • name (symbol)

      describe name param

    • type (Attributor::Type)

      describe type param

    • opts (Hash)

      describe opts param

    • block (Block)

      describe block param

  • #define(name, opts, &block) ⇒ Object

    Assume a type of Attributor::Struct

    Examples:

    attribute :address do
      attribute :number, String
      attribute :street, String
      attribute :city, String
      attribute :state, String
    end

    Parameters:

    • name (symbol)

      describe name param

    • opts (Hash)

      describe opts param

    • block (Block)

      describe block param



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/attributor/dsl_compiler.rb', line 90

def define(name, attr_type = nil, **opts, &block)
  example_given = opts.key? :example
  # add to existing attribute if present
  if (existing_attribute = attributes[name])
    if existing_attribute.attributes
      existing_attribute.type.attributes(&block)
      return existing_attribute
    end
  end

  if attr_type.nil?
    if block_given?
      final_type, carried_options = resolve_type_for_block(name,  **opts, &block)
    else
      final_type, carried_options = resolve_type_for_no_block(name,  **opts)
    end
  else
    final_type = attr_type
    carried_options = {}
  end

  final_opts = opts.dup
  final_opts.delete(:reference) 
  
  # Possibly add a reference for block definitions (No reference for leaves)
  final_opts.merge!(add_reference_to_block(name, opts)) if block_given?
  final_opts = carried_options.merge(final_opts)
  Attributor::Attribute.new(final_type, final_opts, &block)
end

#extra(name, attr_type = nil, **opts, &block) ⇒ Object



56
57
58
59
60
61
62
63
64
65
# File 'lib/attributor/dsl_compiler.rb', line 56

def extra(name, attr_type = nil, **opts, &block)
  if attr_type.nil?
    attr_type = Attributor::Hash.of(key: target.key_type, value: target.value_type)
  end
  target.extra_keys = name
  target.options[:allow_extra] = true
  opts[:default] ||= {}
  attr_type.options[:allow_extra] = true
  key(name, attr_type, **opts, &block)
end

#key(name, attr_type = nil, **opts, &block) ⇒ Object



49
50
51
52
53
54
# File 'lib/attributor/dsl_compiler.rb', line 49

def key(name, attr_type = nil, **opts, &block)
  unless name.is_a?(options.fetch(:key_type, Attributor::Object).native_type)
    raise "Invalid key: #{name.inspect}, must be instance of #{options[:key_type].native_type.name}"
  end
  target.keys[name] = define(name, attr_type, **opts, &block)
end

#parse(*blocks) ⇒ Object



21
22
23
24
25
# File 'lib/attributor/dsl_compiler.rb', line 21

def parse(*blocks)
  blocks.push(Proc.new) if block_given?
  blocks.each { |block| instance_eval(&block) }
  self
end

#resolve_type_for_block(name, **opts) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/attributor/dsl_compiler.rb', line 121

def resolve_type_for_block(name,  **opts)
  resolved_type = nil
  carried_options = {}
  ref = options[:reference]
  if ref && ref.respond_to?(:attributes) && ref.attributes.key?(name)
    type_from_ref = ref.attributes[name]&.type
    resolved_type = type_from_ref < Attributor::Collection ? Attributor::Struct[] : Attributor::Struct
  else
    # Type for attribute with given name could not be determined from reference...or ther is not refrence: defaulting to Struct"
    resolved_type = Attributor::Struct
  end
  [resolved_type, carried_options]
end

#resolve_type_for_no_block(name, **opts) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/attributor/dsl_compiler.rb', line 135

def resolve_type_for_no_block(name,  **opts)
  resolved_type = nil
  carried_options = {}
  ref = options[:reference]
  if ref && ref.respond_to?(:attributes) && ref.attributes.key?(name)
    resolved_type = ref.attributes[name].type
    carried_options = ref.attributes[name].options
  else
    message = "Type for attribute with name: #{name} could not be determined.\n"
    if ref
      message += "You are defining '#{name}' without a type, and the passed in :reference type (#{ref}) does not have an attribute named '#{name}'.\n" \
    else
      message += "You are defining '#{name}' without a type, and there is no :reference type to infer it from (Did you forget to add the type?).\n" \
    end
    message += "\nIf you are omiting a type thinking that would be inherited from the reference, make sure the right one is passed in," \
      ", which has a #{name} defined, otherwise simply specify the type of the attribute you want.\n"
    raise AttributorException, message
  end
  [resolved_type, carried_options]
end