File: builder.rb

package info (click to toggle)
ruby-virtus 2.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 660 kB
  • sloc: ruby: 4,378; makefile: 2
file content (182 lines) | stat: -rw-r--r-- 4,524 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
module Virtus

  # Attribute placeholder used when type constant is passed as a string or symbol
  #
  # @private
  class PendingAttribute
    attr_reader :type, :options, :name

    # @api private
    def initialize(type, options)
      @type, @options = type.to_s, options
      @name = options[:name]
    end

    # @api private
    def finalize
      Attribute::Builder.call(determine_type, options).finalize
    end

    # @api private
    def finalized?
      false
    end

    # @api private
    def determine_type
      if type.include?('::')
        Virtus.constantize(type)
      else
        Object.const_get(type)
      end
    end

  end # PendingAttribute

  # Extracts the actual type primitive from input type
  #
  # @private
  class TypeDefinition
    attr_reader :type, :primitive

    # @api private
    def initialize(type)
      @type = type
      initialize_primitive
    end

    # @api private
    def pending?
      @pending if defined?(@pending)
    end

    private

    # @api private
    def initialize_primitive
      @primitive =
        if type.instance_of?(String) || type.instance_of?(Symbol)
          if !type.to_s.include?('::') && Object.const_defined?(type)
            Object.const_get(type)
          elsif not Attribute::Builder.determine_type(type)
            @pending = true
            type
          else
            type
          end
        elsif not type.is_a?(Class)
          type.class
        else
          type
        end
    end
  end

  class Attribute

    # Builder is used to set up an attribute instance based on input type and options
    #
    # @private
    class Builder
      attr_reader :attribute, :options, :type_definition, :klass, :type

      # @api private
      def self.call(type, options = {})
        type_definition = TypeDefinition.new(type)

        if type_definition.pending?
          PendingAttribute.new(type, options)
        else
          new(type_definition, options).attribute
        end
      end

      # @api private
      def self.determine_type(klass, default = nil)
        type = Attribute.determine_type(klass)

        if klass.is_a?(Class)
          type ||=
            if klass < Axiom::Types::Type
              determine_type(klass.primitive)
            elsif EmbeddedValue.handles?(klass)
              EmbeddedValue
            elsif klass < Enumerable && !(klass <= Range)
              Collection
            end
        end

        type || default
      end

      # @api private
      def initialize(type_definition, options)
        @type_definition = type_definition

        initialize_class
        initialize_type
        initialize_options(options)
        initialize_default_value
        initialize_coercer
        initialize_attribute
      end

      private

      # @api private
      def initialize_class
        @klass = self.class.determine_type(type_definition.primitive, Attribute)
      end

      # @api private
      def initialize_type
        @type = klass.build_type(type_definition)
      end

      # @api private
      def initialize_options(options)
        @options = klass.options.merge(:coerce => Virtus.coerce).update(options)
        klass.merge_options!(type, @options)
        determine_visibility
      end

      # @api private
      def initialize_default_value
        options.update(:default_value => DefaultValue.build(options[:default]))
      end

      # @api private
      def initialize_coercer
        options.update(:coercer => determine_coercer)
      end

      # @api private
      def initialize_attribute
        @attribute = klass.new(type, options)

        @attribute.extend(Accessor)     if options[:name]
        @attribute.extend(Coercible)    if options[:coerce]
        @attribute.extend(NullifyBlank) if options[:nullify_blank]
        @attribute.extend(Strict)       if options[:strict]
        @attribute.extend(LazyDefault)  if options[:lazy]

        @attribute.finalize if options[:finalize]
      end

      # @api private
      def determine_coercer
        options.fetch(:coercer) { klass.build_coercer(type, options) }
      end

      # @api private
      def determine_visibility
        default_accessor  = options.fetch(:accessor)
        reader_visibility = options.fetch(:reader, default_accessor)
        writer_visibility = options.fetch(:writer, default_accessor)
        options.update(:reader => reader_visibility, :writer => writer_visibility)
      end

    end # class Builder

  end # class Attribute
end # module Virtus