How to resolve Generics in macros? - crystal-lang

macro m(type)
{% puts type.resolve %}
end
module Foo
class Bar
end
class Baz
m(Array(Bar))
end
end
# in temp4.cr:10: undefined macro method 'Generic#resolve'
#
# m(Array(Bar))
I need to resolve Array(Bar) so it prints type with full paths (Array(Foo::Bar)). Is there a convenient way of doing so regardless of the type complexity (e.g. Array(Bar, Array(SomeOtherType) | Int32))?

Resolved by this PR: https://github.com/crystal-lang/crystal/pull/6617. Thanks Ary!

Related

Crystal lang : method_missing for class object?

Something like this?
class A
module ClassMethods
macro method_missing(call)
{% p call %}
end
end
extend ClassMethods
end
A.a
Result of the code is Error : undefined method 'a' for A:Class. It seems method_missing doesn't work for class object.
The reason for the question is that I implemented a class with a current class method to get an instance for current context. For the simplification, I want some like forward_missing_to to replace MyClass.current.xxx with MyClass.xxx, so method_missing is required according forward_missing_to's implementation.
I also found, delegate works fine for class methods using above way. So actually my pratical problem has been solved, but I'm still interesting with the question : method_missing for class object, is it impossible?

Are double splats treated differently in methods?

When I used a double splat for a method, I'm not allowed to define a variable with a type within the method:
def show(**attrs)
place : String = "w"
puts place
end
show(name: "Bobby") # This works.
show(name: "Bobby", place: "World") # This fails:
#
#in tmp.cr:2: variable 'place' already declared
#
# place : String = "w"
# ^~~~~
Is this the expected behaviour when using double splats? I couldn't find anything in the Crystal Book about this: https://crystal-lang.org/docs/syntax_and_semantics/splats_and_tuples.html
This is a bug, please report it as such.
Note that declaring local variables with a type is not a recommended practice. Because it was a recent addition, it is not well tested and apparently prone to bugs.
You can see that this works, anyway:
def show(**attrs)
place = "w"
puts place
puts attrs[:place]
end
show(name: "Bobby", place: "World")
w
World

How to structure namespaced modules

I am having trouble with Ruby class (constant) lookup within the context of a Rails engine gem.
I have a gem MyGem that is a Rails engine. It defines non-namespaced models that are expected to be overridden by the MainApp that would include the gem and namespaced modules, which are included in gem's and main_app's models to have a DRY way of defining reusable code.
Here is a sample code structure:
Two models
# in /app/models/user.rb
class User < ActiveRecord::Base
include MyGem::User::CommonExt
end
# in /app/models/comment.rb
class Comment < ActiveRecord::Base
include MyGem::Comment::CommonExt
end
Their two modules
# in /app/models/concerns/my_gem/user/common_ext.rb
module MyGem::User::CommonExt
def load_comment(id)
return Comment.find(id)
end
end
# in /app/models/concerns/my_gem/comment/common_ext.rb
module MyGem::Comment::CommonExt
def load_user(id)
return User.find(id)
end
end
Now, if I call
User.new.load_comment(1)
I get undefined method #find for MyGem::Comment::Module
I think I understand why this is happening - in the context of #load_comment definition, which is namespaced under MyGem, Comment constant lookup returns MyGem::Comment, rather than the more distant ::Comment
I would prefer not to have to prepend every model instance with ::.
Is there a file structure, model/class definition or configuration change I could use to make a call to Comment return the model Comment, not the MyGem::Comment module?
I would use inheritance instead of mixin in this case.
So in your gem/engine you could define your common class similar to this:
module MyGem
module Common
class Base < ActiveRecord::Base
# common functionality goes here
def load(record_type, id)
record_type.find(id)
end
end
end
end
Then in your main_app code:
class User < MyGem::Common::Base
...
end
Now you could do this:
User.new.load(Comment, 1)
This violates the Law of Demeter but hopefully it illustrates the point.
Doing it like this is DRY and has the added benefit that it prevents your gem from having to know about classes which are outside it's own scope.

Is there a way to check if part of the URL contains a certain string

Is there a way to check if part of the URL contains a certain string:
Eg. <% if current_spree_page?("/products/*") %>, where * could be anything?
I tested, and gmacdougall's answer works, I had already found a solution though.
This is what I used to render different layouts depending on what the url is:
url = request.path_info
if url.include?('products')
render :layout => 'product_layout'
else
render :layout => 'layout'
end
The important thing to note is that different pages will call different methods within the controller (eg. show, index). What I did was put this code in its own method and then I am calling that method where needed.
If you are at a place where you have access to the ActionDispatch::Request you can do the following:
request.path.start_with?('/products')
You can use include? method
my_string = "abcdefg"
if my_string.include? "cde"
puts "String includes 'cde'"
end`
Remember that include? is case sensetive. So if my_string in the example above would be something like "abcDefg" (with an uppercase D), include?("cde") would return false. You may want to do a downcase() before calling include?()
The other answers give absolutely the cleanest ways of checking your URL. I want to share a way of doing this using a regular expression so you can check your URL for a string at a particular location in the URL.
This method is useful when you have locales as first part of your URL like /en/users.
module MenuHelper
def is_in_begin_path(*url_parts)
url_parts.each do |url_part|
return true if request.path.match(/^\/\w{2}\/#{url_part}/).present?
end
false
end
end
This helper method picks out the part after the second slash if the first part contains 2 word characters as is the case if you use locales. Drop this in your ApplicationController to have it available anywhere.
Example:
is_in_begin_path('users', 'profile')
That matches /en/users/4, /en/profile, /nl/users/9/statistics, /nl/profile etc.

Erlydtl: is there a way to render list of records in template?

For example I have:
-record(usr,{name,email}).
...
Usr1 = #usr{name="John", email="john#host.com"},
Usr2 = #usr{name="Jane", email="jane#host.com"},
Usr3 = #usr{name="Alex", email="alex#host.com"},
{ok, Result} = template_dtl:render([{users, [Usr1, Usr2, Usr3]}]),
...
and I want to use it like:
{% block content %}
{% for user in users %}
Send mail to {{user.name}}
{% endfor %}
{% endblock %}
Anybody have faced with same problem?
Records are syntactic sugar on top of tuples. Usr1, Usr2 and Usr3 just are tuples, and precisely:
Usr1 = {usr, "John", "john#host.com"},
Usr2 = {usr, "Jane", "jane#host.com"},
Usr3 = {usr, "Alex", "alex#host.com"}.
The template does not know how to interpret these records, as it doesn't know the record definition at compile time.
There are three solutions to your problem. In all cases, the template will be the same and you should write user.email and user.name as you did.
Teach erlydtl about your records
record_info compile option is precisely meant to tell erlydtl about the records used in template variables.
erlydtl:compile_template(Template, TemplateModuleName, [{record_info, [{usr, record_info(fields, usr)}]).
The drawback is that you might not call erlydtl:compile* yourself and therefore adding record_info option can prove difficult. Besides, the piece of code that calls this function must know the record definition which you will probably have to move to an .hrl file.
Transform your records to a dict:dict(), a proplist() or a gb_trees:tree()
This is what Soup d'Campbells suggests in their comment. You can also use compile-time function record_info/2 for this purpose. The easiest indeed is the proplist() form:
lists:zip(record_info(fields, usr), tl(tuple_to_list(Usr1))).
tuple_to_list(Usr1) evaluates to [usr, "John", "john#host.com"], while record_info(fields, usr) is equal to [name, email].
Encapsulate data in modules
Records are not really good to capture data structures with public accessors (user.name) because they are better maintained locally to a given module, as this makes code updates much easier. Alternatively, you could define a module (called usr or app_user but not user as such a module already exists) that would export name/1 and email/1 accessors.
Erlydtl magic here is based on the feature previously known as parametrized modules, and more precisely the ability to call a function using a tuple, instead of an atom, as a module name. You do not really need a parametrized module, just pass a tuple matching an existing module.
For example, your app_user module could look like this:
-module(app_user).
-export([new/2, name/1, email/1]).
-record(?MODULE, {name :: string(), email :: string()}). % private to this module.
-type app_user() :: #?MODULE{}.
-spec new(string(), string()) -> app_user().
new(Name, Email) -> #?MODULE{name = Name, email = Email}.
-spec name(app_user()) -> string().
name(#?MODULE{name = Name}) -> Name.
-spec email(app_user()) -> string().
email(#?MODULE{email = Email}) -> Email.
(?MODULE is used instead of app_user because this only works if the record's name matches the name of the module, and this code will work even if you rename the module).
Then, in your code, instead of:
Usr1 = #usr{name = "John", email = "john#host.com"}
You would write:
Usr1 = app_user:new("John", "john#host.com").
Usr1 is just a record, or more precisely the tuple:
{app_user, "John", "john#host.com"}.
Erlydtl will nevertheless be able to process Usr1 directly. It will consider it a parametrized module, as app_user exists as a module (it's the module above). Therefore, during template rendering, it will call app_user:name/1 and app_user:email/1 accessor functions, passing them the whole record.