Module: Schema::ClassMethods

Defined in:
lib/taco/schema.rb

Instance Method Summary collapse

Instance Method Details

#schema_attr(name, opts) ⇒ Object

Raises:

  • (TypeError)


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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/taco/schema.rb', line 85

def schema_attr(name, opts)
  @schema_attrs ||= {}

  raise TypeError.new("attribute #{name}: missing or invalid :class") unless opts[:class].is_a?(Class)

  if opts[:default].nil?
    opts[:default] = case opts[:class].to_s # can't case on opts[:class], because class of opts[:class] is always Class :-)
    when 'String'
      ''
    when 'Fixnum'
      0
    when 'Time'
      lambda { Time.new }
    else
      raise ArgumentError.new("Sorry, no default default exists for #{opts[:class]}")
    end
  end

  unless opts[:default].is_a?(opts[:class]) || opts[:default].is_a?(Proc)
    raise TypeError.new("attribute #{name}: invalid :default")
  end

  if opts[:validate]
    unless opts[:validate].is_a?(Array) || opts[:validate].is_a?(Proc)
      raise ArgumentError.new("attribute #{name}: expecting Array or Proc for :validate")
    end

    if opts[:validate].is_a?(Array)
      raise TypeError.new("attribute #{name}: wrong type in :validate Array") unless opts[:validate].all? { |v| v.is_a?(opts[:class]) }
    end
  end

  raise ArgumentError.new("attribute #{name}: already exists") if @schema_attrs[name]

  @schema_attrs[name] = opts

  value_getter = if opts[:default].is_a?(Proc)
    %Q(
        opts = self.class.schema_attributes[:#{name}]
        value = opts[:default].call
        raise TypeError.new("attribute #{name}: expected type #{opts[:class]}, received \#{value.class}") unless opts[:class] == value.class
    )
  else
    %Q(value = #{opts[:default].inspect})
  end
  module_eval %Q(
                  def #{name}
                    if @#{name}.nil?
                      #{value_getter}
                      self.#{name}= value
                    end
                    @#{name}
                  end
                )

  unless opts[:coerce] == false # possible values are false=no-coerce, nil=default-coerce, Proc=custom-coerce
    case opts[:class].to_s # can't case on opts[:class], because class of opts[:class] is always Class :-)
    when 'Fixnum'
      unless opts[:coerce].is_a? Proc
        # the default coercion for Fixnum
        opts[:coerce] = lambda do |value|
          unless value.is_a?(Fixnum) # FIXME: this "unless value.is_a?(same class as 'when')" is copy-pasta.  fix it.
            raise TypeError.new("attribute #{name}: cannot coerce from \#{value.class}") unless value.is_a?(String)
            i = value.to_i
            raise TypeError.new("attribute #{name}: failed to coerce from \#{value}") unless i.to_s == value
            value = i
          end
          value
        end
      end
    when 'Time'
      unless opts[:coerce].is_a? Proc
        # the default coercion for Time
        opts[:coerce] = lambda do |value|
          unless value.is_a?(Time) # FIXME: this "unless value.is_a?(same class as 'when')" is copy-pasta.  fix it.
            raise TypeError.new("attribute #{name}: cannot coerce from \#{value.class}") unless value.is_a?(String)
            begin
              value = Time.parse(value)
            rescue ArgumentError
              raise TypeError.new("attribute #{name}: cannot coerce from \#{value.class}")
            end
          end
          value
        end
      end
    end

    coerce = 'value = opts[:coerce].call(value)' if opts[:coerce]
  end

  unless opts[:transform] == false # possible values are false=no-transform, nil=default-transform, Proc=custom-transform
    case opts[:class].to_s # can't case on opts[:class], because class of opts[:class] is always Class :-)
    when 'String'
      unless opts[:transform].is_a? Proc
        # the default transform for String: remove excess whitespace
        opts[:transform] = lambda { |s| s.strip }
      end
    when 'Time'
      unless opts[:transform].is_a? Proc
        # the default transform for Time: remove subsecond precision.  subsec precision is not recorded in to_s, so unless
        #                                 we scrub it out, the following happens:
        #                                 foo.a_time = Time.new
        #                                 Time.parse(foo.a_time.to_s) == foo.a_time # returns false most of the time!
        opts[:transform] = lambda { |t| Time.new t.year, t.mon, t.day, t.hour, t.min, t.sec, t.utc_offset }
      end
    end

    transform = 'value = opts[:transform].call(value)' if opts[:transform]
  end

  callback = %Q(
    if self.respond_to? :schema_attribute_change
      self.schema_attribute_change(:#{name}, @#{name}, value)
    end
  )

  setter_method = %Q(
    def #{name}=(value)
      opts = self.class.schema_attributes[:#{name}]
      #{coerce}
      raise TypeError.new("attribute #{name}: expected type #{opts[:class]}, received \#{value.class}") unless opts[:class] == value.class
      #{transform}
      #{callback}
      @#{name} = value
    end
  )
  module_eval setter_method
  module_eval "private :#{name}=" unless opts[:settable]
end

#schema_attr_expand(prefix) ⇒ Object

Raises:

  • (KeyError)


59
60
61
62
63
64
# File 'lib/taco/schema.rb', line 59

def schema_attr_expand(prefix)
  candidates = @schema_attrs.keys.select { |a| a.to_s.start_with? prefix }
  raise KeyError.new("no attribute is prefixed with #{prefix}") if candidates.size == 0
  raise KeyError.new("prefix #{prefix} is not unique") if candidates.size > 1
  candidates[0]
end

#schema_attr_remove(name) ⇒ Object

Raises:

  • (KeyError)


66
67
68
69
70
71
# File 'lib/taco/schema.rb', line 66

def schema_attr_remove(name)
  raise KeyError.new("attribute #{name}: does not exist in class #{self.name}") unless @schema_attrs.include? name
  @schema_attrs.delete(name)
  self.send(:remove_method, name)
  self.send(:remove_method, "#{name}=".to_s)
end

#schema_attr_replace(name, opts) ⇒ Object

Raises:

  • (KeyError)


73
74
75
76
77
# File 'lib/taco/schema.rb', line 73

def schema_attr_replace(name, opts)
  raise KeyError.new("attribute #{name}: does not exist in class #{self.name}") unless @schema_attrs.include? name
  schema_attr_remove(name)
  schema_attr(name, opts)
end

#schema_attr_update(name, opts) ⇒ Object

Raises:

  • (KeyError)


79
80
81
82
83
# File 'lib/taco/schema.rb', line 79

def schema_attr_update(name, opts)
  raise KeyError.new("attribute #{name}: does not exist in class #{self.name}") unless @schema_attrs.include? name
  raise KeyError.new("attribute #{name}: cannot update non-settable attribute") unless @schema_attrs[name][:settable]
  schema_attr_replace(name, @schema_attrs[name].merge(opts))
end

#schema_attributesObject



55
56
57
# File 'lib/taco/schema.rb', line 55

def schema_attributes
  @schema_attrs
end