# Undefined local variable or method

**URL:** <https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934>\
**Category:** Help & Support\
**Created:** [October 13, 2021, 7:49am UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934 "2021-10-13T07:49:33Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![henrikac](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/henrikac/32/3079_2.png) [@henrikac](https://forum.crystal-lang.org/u/henrikac)\
**Post date:** [October 13, 2021, 7:49am UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/1 "2021-10-13T07:49:33Z")

</div>

I am working on a new shard [kemal-form](https://github.com/henrikac/kemal-form) that tries to help validate forms. I am now trying to make a new validator **EqualTo** that is supposed to make sure that the value of two fields are identical.

I have this macro that generates fields for forms

```crystal
macro field(decl, **options)
  {% raise "must be type Kemal::Form::FormField" unless decl.type.resolve < Kemal::Form::FormField %}
  {%
    field_name = decl.var
    field_type = decl.type

    id_attr = options[:id]
    if id_attr.nil?
      id_attr = field_name.id.stringify
    end

    name_attr = options[:name]
    if name_attr.nil?
      name_attr = field_name.id.stringify
    end

    extra_attrs = options[:attrs]

    field_value = options[:value]
    if field_value.nil?
      field_value = ""
    end

    required = false
    if options[:required] == true
      required = true
    end

    label = options[:label]
    if label.nil?
      label_for = id_attr
      label_text = id_attr.titleize
    end

    validators = options[:validators]
  %}

  @{{field_name}} : {{field_type}} = {{field_type.id}}.new(
    id: {{id_attr}},
    name: {{name_attr}},
    attrs: {{extra_attrs}},
    value: {{field_value}},
    required: {{required}},
    {% if validators.nil? %}
      validators: [] of Kemal::FormValidator::Validator,
    {% else %}
      validators: {{validators.id}} of Kemal::FormValidator::Validator,
    {% end %}
    {% if label.nil? %}
      label: Kemal::Form::Label.new({{label_for}}, {{label_text}}, nil))
    {% else %}
      label: {{label}})
    {% end %}

  def {{field_name.id}} : {{field_type.id}}
    @{{field_name}}
  end
end

```

_ **simplified version of `EqualTo`** _

```crystal
class EqualTo < Kemal::FormValidator::Validator
  def initialize(@compare_field : Kemal::Form::FormField); end
end

```

but when I initialize a new `EqualTo`

```crystal
class MyForm < Kemal::Form
  field password : Kemal::Form::PasswordField
  field confirm_password : Kemal::Form::PasswordField, 
                            validators: [
                              Kemal::FormValidator::EqualTo.new(password)
                            ]
end

```

I will get this error: `Error: undefined local variable or method 'password' for MyForm.class`.

I have tried to figure out how to solve this for a few days now but still no luck ☹

---

<div class="post-metadata">

**Author:** ![jhass](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jhass/32/1779_2.png) [@jhass](https://forum.crystal-lang.org/u/jhass)\
**Post date:** [October 13, 2021, 9:08am UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/2 "2021-10-13T09:08:54Z")

</div>

I think you simplified the macro a bit too much here, you’re not making any use of `options` there which is the critical part.

---

<div class="post-metadata">

**Author:** ![henrikac](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/henrikac/32/3079_2.png) [@henrikac](https://forum.crystal-lang.org/u/henrikac)\
**Post date:** [October 13, 2021, 9:38am UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/3 "2021-10-13T09:38:14Z")

</div>

Sorry, I updated the macro code snippet

---

<div class="post-metadata">

**Author:** ![jhass](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jhass/32/1779_2.png) [@jhass](https://forum.crystal-lang.org/u/jhass)\
**Post date:** [October 13, 2021, 11:26am UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/4 "2021-10-13T11:26:23Z")

</div>

So you’re generating

```auto
class MyForm < Kemal::Form
  # ...
  @confirm_password : Kemal::Form::PasswordField = Kemal::Form::PasswordField.new(
    # ...
    validators: [Kemal::FormValidator::EqualTo.new(password)] of Kemal::Form::Validator
  )
end

# Or to simplify

class Foo
  @confirm_password = Bar.new(password)

  def password
  end
end

```

I hope it is evident that you cannot refer to an instance method at the class level.

If it’s an option for you to generate the class initializer then you could try to move the initialization there, something akin to

```auto
class MyForm < Kemal::Form
  # ...
  @confirm_password : Kemal::Form::PasswordField

   def initialize
    @confirm_password = Kemal::Form::PasswordField.new(
      # ...
      validators: [Kemal::FormValidator::EqualTo.new(password)] of Kemal::Form::Validator
    )
   end
end

```

If that’s not an option then you have to redesign either the API of `PasswordField` or `EqualTo` to take any parameters in the form of something with delayed execution, that is as a block, which is potentially generated by the macro code. Sensible variants could be yielding the model object into the validator or a “validator list provider”

```auto
class MyForm < Kemal::Form
  # ...
  @confirm_password : Kemal::Form::PasswordField = Kemal::Form::PasswordField.new(
    # ...
    validators: [Kemal::FormValidator::EqualTo.new(&.password)] of Kemal::Form::Validator
  )
end

# ---

class MyForm < Kemal::Form
  # ...
  @confirm_password : Kemal::Form::PasswordField = Kemal::Form::PasswordField.new(
    # ...
    validators: -> (form : MyForm) { [Kemal::FormValidator::EqualTo.new(form.password)] }
  )
end

```

I hope there’s some ideas for you in here.

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [October 13, 2021, 12:06pm UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/5 "2021-10-13T12:06:01Z")

</div>

Actually, this looks like a bug. You should be able to call an instance method from an instance variable initializer.

That said, the initializer seems to be resolved in the class scope, not the instance scope, so changing that would be a breaking change. I have no idea why that’s implemented that way… 🤷‍♂️

---

<div class="post-metadata">

**Author:** ![henrikac](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/henrikac/32/3079_2.png) [@henrikac](https://forum.crystal-lang.org/u/henrikac)\
**Post date:** [October 13, 2021, 12:15pm UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/6 "2021-10-13T12:15:19Z")

</div>

Of the possible solutions given I like your last suggestion the most.

But I am still confused because I thought that macro code would be expanded into “real” code before the code is compiled and run? If this is true then I would expect it to be possible to use instance variables as arguments to initializers and other methods?

---

<div class="post-metadata">

**Author:** ![henrikac](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/henrikac/32/3079_2.png) [@henrikac](https://forum.crystal-lang.org/u/henrikac)\
**Post date:** [October 13, 2021, 12:16pm UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/7 "2021-10-13T12:16:41Z")

</div>

> [@asterite](#):
>
> I have no idea why that’s implemented that way

Are you talking about my code or crystal?

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [October 13, 2021, 12:36pm UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/8 "2021-10-13T12:36:07Z")

</div>

> [@henrikac](#):
>
> Are you talking about my code or crystal?

Crystal.

---

<div class="post-metadata">

**Author:** ![bcardiff](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/bcardiff/32/3_2.png) [@bcardiff](https://forum.crystal-lang.org/u/bcardiff)\
**Post date:** [October 13, 2021, 12:56pm UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/9 "2021-10-13T12:56:35Z")

</div>

We prevented that to avoid dependencies between ivar initializers

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [October 13, 2021, 12:57pm UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/10 "2021-10-13T12:57:06Z")

</div>

> [@henrikac](#):
>
> I am now trying to make a new validator **EqualTo**

If it would be helpful you could look into integrating [Validator](https://athenaframework.org/Validator). Follows a similar pattern of having classes to represent the constraints, but also supports multiple violations at once, among other features.

---

<div class="post-metadata">

**Author:** ![henrikac](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/henrikac/32/3079_2.png) [@henrikac](https://forum.crystal-lang.org/u/henrikac)\
**Post date:** [October 14, 2021, 8:03am UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/11 "2021-10-14T08:03:15Z")

</div>

I’ll take look, thank you 🙂

---

<div class="post-metadata">

**Author:** ![rogerdpack](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/rogerdpack/32/117_2.png) [@rogerdpack](https://forum.crystal-lang.org/u/rogerdpack)\
**Post date:** [October 15, 2021, 1:56am UTC](https://forum.crystal-lang.org/t/undefined-local-variable-or-method/3934/12 "2021-10-15T01:56:15Z")

</div>

FWIW you can see what code is generated from macros: [Is there a way to see what a Crystal macro expands to? - Stack Overflow](https://stackoverflow.com/questions/62852621/is-there-a-way-to-see-what-a-crystal-macro-expands-to)

I wonder if the error message could be expanded to explain more?
