amberframework / amber

A Crystal web framework that makes building applications fast, simple, and enjoyable. Get started with quick prototyping, less bugs, and blazing fast performance.
https://amberframework.org
MIT License
2.58k stars 208 forks source link

automatically create paths helpers #778

Open jaysneg opened 6 years ago

jaysneg commented 6 years ago

Description

Add automatically create paths and URL Helpers like at Rails

Steps to Reproduce

http://guides.rubyonrails.org/routing.html#path-and-url-helpers

Versions

0.7.2

faustinoaq commented 6 years ago

Hi @jaysneg Thank you for open this issue!

I think amber already support resources. Perhaps we just need to add more capabilities to it,

@amberframework/contributors WDYT?

robacarp commented 6 years ago

I agree, this will be a good feature to have. Here is my current workaround. I have a file at src/controllers/application/url_helpers.cr which is included into my application_controller and has code like this:


module UrlHelpers
  def root_path
    if current_user.guest? || ! current_user.activated?
      "/"
    else
      domains_path
    end
  end

  def users_path
    "/users"
  end

  def user_path(user : User)
    "/user/#{user.id}"
  end

  def new_user_path
    "/me/register"
  end

  def invited_user_registration_path(invite : Invite)
    "/me/register?invite=#{invite.code}"
  end

  def user_activation_path
    "/me/activate"
  end

# on and on for 150 lines
end
eliasjpr commented 6 years ago

I think this is a great Idea.

Some ideas for this would be to include action type check support so for instance if the action does not exist then it throws an error similar to the way Lucky does it.

This basically can introspect the controller action methods to generate the helpers dynamically. And maybe instead of creating methods for it we can create something more intuitive User::New.with(:id)

For each controller define in routes we can dynamically generate helpers as

# Routes
post "/users/activation/:id#integer", UsersController, :activation

# Generated helper
Users::Activation.path(user_id: interger)
shobhitic commented 6 years ago

@eliasjpr If we generate routes based on controller and action, and not based on the actual path, we will never be able to have a single action be accessible at two different paths.

robacarp commented 6 years ago

@eliasjpr @shobhitic generating the named routes based off of controller and action seems like a fine way to provide a default. As long as there as a way to override the default, I'm in favor. Rails does this surprisingly well with some very simple method declarations.

shobhitic commented 6 years ago

@robacarp IMO, it's not the best default. Lots of times while refactoring, you tend to move an action from one controller to the other while keeping the path to access it same. Happens all the time while refactoring APIs because you can't change routes there. In situations like these, you'll have to change all the references to path, unlike in Rails where the path method is based on the path being used, and not how it's implemented.

I think we should also create path methods based on paths, and not based on their implementations.

eliasjpr commented 6 years ago

@robacarp I would suggest the above example as a start point, I believe if you want to define a custom path that should be more of a global path helper method.

I am also fine with the way Rails does this, but having some checks at compile time would be very beneficial this would avoid having dead URL paths in a large app.

anamba commented 5 years ago

I'm not sure this is something that can be generalized and implemented at the framework level. But the docs could add some implementation suggestions or the generator could create a helper, perhaps.

In the spirit of providing more ideas/working code examples, here's what I do:

  macro resource_paths(klass, plural, to_param_method = :id)
    {% singular = klass.id.underscore %}
    def {{plural.id}}_path
      "/{{plural.id}}"
    end
    def new_{{singular.id}}_path(**params)
      qs = params.map { |k, v| "#{URI.escape(k.to_s)}=#{URI.escape(v.to_s)}" }.join("&")
      "/{{plural.id}}/new" + (qs.blank? ? "" : "?#{qs}")
    end
    def {{singular.id}}_path({{singular.id}} : {{klass.id}})
      "/{{plural.id}}/#{{{singular.id}}.{{to_param_method.id}}}"
    end
    def edit_{{singular.id}}_path({{singular.id}} : {{klass.id}})
      "/{{plural.id}}/#{{{singular.id}}.{{to_param_method.id}}}/edit"
    end

    def link_to(body : String, obj : {{klass.id}}, **options)
      link_to(body, {{singular.id}}_path(obj), **options)
    end
    def link_to(body : String, obj : {{klass.id}}, **options, &block)
      link_to(body, {{singular.id}}_path(obj), **options, &block)
    end

    def redirect_to(obj : {{klass.id}}, **args)
      redirect_to({{singular.id}}_path(obj), **args)
    end
  end
  resource_paths User, :users
elaine-jackson commented 5 years ago

@eliasjpr It gets a 99 on Desktop and 93 on Mobile, that's way better than most websites get. Is this a priority for Amber still?

eliasjpr commented 5 years ago

Would love to see the the resources macro generate path and url helpers. Anyone interested in taking this on?

alex-min commented 4 years ago

I use this code I wrote on my case to generate the routes.

This might not the cleanest way to do it but it works, to use it: