# Struggle on define a variable in macro, use it outside where expanding it

**URL:** <https://forum.crystal-lang.org/t/struggle-on-define-a-variable-in-macro-use-it-outside-where-expanding-it/4726>\
**Category:** Help & Support\
**Created:** [June 25, 2022, 7:32pm UTC](https://forum.crystal-lang.org/t/struggle-on-define-a-variable-in-macro-use-it-outside-where-expanding-it/4726 "2022-06-25T19:32:35Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [June 25, 2022, 7:32pm UTC](https://forum.crystal-lang.org/t/struggle-on-define-a-variable-in-macro-use-it-outside-where-expanding-it/4726/1 "2022-06-25T19:32:35Z")

</div>

I am struggle on migration one of our small go project to use `kemal + avaram`.

for avoid duplicate code, i try extract some common code (which in rails, we treat as before\_action) into macro,  
Maybe i do things wrong, it that is it, please point it out the correct way, anyway, let me show one example:

```crystal
# define a macro here for used in several blocks
private macro get_user(env)
  open_id = env.params.url["open_id"]
  user = UserQuery.new.open_id(open_id).first # <= i define a user variable in macro.

  if user.nil?
	env.response.status_code = 400
	next "Error"
  end
  {% debug %}
end

post path.admin_update_user do |env|
   get_user(env) # <= use macro here, the user can be visited in ECR template.
   # ...
end

post path.admin_update_user do |env|
   get_user(env) # <= use macro here too
   

   # i try to use `user` object here, but no luck.
   UpdateUser.update!(user, env.params)
end

```

As you can see, i get compile time error, like this:

```auto
Error: undefined method 'user' for top-level

If you declared 'user' in a suffix if, declare it in a regular if for this to work. 
If the variable was declared in a macro it's not visible outside it)

```

So, use `user` outside macro is not supported, right?

What is the correct way to do things like this? as you can see, what i want is just a  
before\_action like things, which can return early if user is nil.

Thank you.

---

<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:** [June 25, 2022, 7:41pm UTC](https://forum.crystal-lang.org/t/struggle-on-define-a-variable-in-macro-use-it-outside-where-expanding-it/4726/2 "2022-06-25T19:41:48Z")

</div>

I think the gist of the problem is the macro isn’t expanded yet when the compiler encounters the update method call.

Doing something like this seems to work:

```auto
get_user(env)
{% begin %}
  UpdateUser.update!(user, env.params)
{% end %}

```

Tho technically speaking using a macro in this context actually results in _more_ code in the program. Could probably just use a method for this.

EDIT: A method might be a bit tricky with how kemal handles error handling. I.e. you need to exit early from the block, which you can’t do from outside of it…

EDIT2: You also dont really need to pass `env` to the macro, as you’re not using that parameter, nor would it work the way you’re thinking.

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [June 26, 2022, 7:35am UTC](https://forum.crystal-lang.org/t/struggle-on-define-a-variable-in-macro-use-it-outside-where-expanding-it/4726/3 "2022-06-26T07:35:15Z")

</div>

> [@Blacksmoke16](#):
>
> EDIT2: You also dont really need to pass `env` to the macro, as you’re not using that parameter, nor would it work the way you’re thinking.

Yes, my fault, env is not necessary for this case

> [@Blacksmoke16](#):
>
> ```auto
> {% begin %}
> UpdateUser.update!(user, env.params)
> {% end %}
> 
> ```

this seem like not work.

---

<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:** [June 26, 2022, 8:34am UTC](https://forum.crystal-lang.org/t/struggle-on-define-a-variable-in-macro-use-it-outside-where-expanding-it/4726/4 "2022-06-26T08:34:46Z")

</div>

Macros can’t define local variables.

---

<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:** [June 26, 2022, 12:54pm UTC](https://forum.crystal-lang.org/t/struggle-on-define-a-variable-in-macro-use-it-outside-where-expanding-it/4726/5 "2022-06-26T12:54:38Z")

</div>

> [@zw963](#):
>
> this seem like not work.

It deff, does. I just tried it:

```auto
require "kemal"

class UserQuery
  def open_id(id)
    pp id
    [1, 2, 3]
  end
end

module UpdateUser
  def self.update!(u, params)
    pp u
  end
end

# define a macro here for used in several blocks
private macro get_user(env)
  open_id = env.params.url["open_id"]
  user = UserQuery.new.open_id(open_id).first # <= i define a user variable in macro.

  if user.nil?
    env.response.status_code = 400
    next "Error"
  end
end

post "/bar/:open_id" do |env|
  get_user(env) # <= use macro here too
  {% begin %}
    # i try to use `user` object here, but no luck.
    UpdateUser.update!(user, env.params)
  {% end %}
end

Kemal.run

```

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [June 26, 2022, 1:19pm UTC](https://forum.crystal-lang.org/t/struggle-on-define-a-variable-in-macro-use-it-outside-where-expanding-it/4726/6 "2022-06-26T13:19:00Z")

</div>

Oops, it my fault.

it not works for me because my code is included in a macro, like this:

```auto
module AdminUserController
  macro included
    # my code is here.
	macro get_user
      open_id = env.params.url["open_id"]
      user = UserQuery.new.open_id(open_id).first

	  if !user.nil?
		env.response.status_code = 400
		next "Error"
	  end

      user
    end
	
	get ... do
	  \{% begin %}
	    ...
	  \{% end }	
	end
  end
end

```

i have to escaped it to make it works.

---

<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:** [June 26, 2022, 1:21pm UTC](https://forum.crystal-lang.org/t/struggle-on-define-a-variable-in-macro-use-it-outside-where-expanding-it/4726/7 "2022-06-26T13:21:07Z")

</div>

Here’s an advice that will save you a lot of trouble: don’t use macros. You don’t need them. I’m sure of it. Please rewrite your code in a different way. For example `get_user` could return `User` or `nil`, and respond with an error in case of `nil`. Then you use it like this:

```crystal
get_user(env) || next "Error"

```

I recommend not using macros at all in the beginning. Macros are mostly used for library code, not for application code.

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [June 26, 2022, 1:43pm UTC](https://forum.crystal-lang.org/t/struggle-on-define-a-variable-in-macro-use-it-outside-where-expanding-it/4726/8 "2022-06-26T13:43:29Z")

</div>

Yes! i consider more deeper understood the macro … it not elixir
