Module: Brut::SinatraHelpers::ClassMethods

Defined in:
lib/brut/sinatra_helpers.rb

Instance Method Summary collapse

Instance Method Details

#action(path) ⇒ Object

Declare a form action that has no associated form elements. This is used when you need to use a button to submit to the back-end, and the route contains all the context you need. For example a post to /approved_widgets/:id communicates that the Widget with ID :id can be approved.

This is preferred over path because a) it's more explicit that this is handling a POST from some HTML and b) this will check to make sure there is no form defined.



137
138
139
140
# File 'lib/brut/sinatra_helpers.rb', line 137

def action(path)
  route = Brut.container.routing.register_handler_only(path)
  self.define_handled_route(route, type: :action)
end

#form(path) ⇒ Object

Declares a form that will be submitted to the app. To handle the submission you must providate a handler and an optional form. The form defines all the fields in your form, including constraints. These can be used to generate HTML for the form. When the form is submitted to your app, the form is instantiated and filled in with all the values it is requesting. That form is then passed off to the configured handler. The handle! method performs whatever processing is needed.

If you have no form elements and are just responding to a POST action from a browser, use action.

The name of the classes are based on a convention similar to page:

  • Each part of the path that is not a placeholder will be camelized
  • Any part of the path that is a placholder has its leading colon removed, then is camelized, but appended to the previous part with With, thus WidgetsWithId is created from Widgets, With, and Id.
  • The final part of the path is further appended with Form or Handler.
  • These parts now make up a path to a class, so the entire thing is joined by :: to form the fully-qualified class name.

Examples:

  • form("/widgets") will use WidgetsForm and WidgetsHandler
  • form("/widgets/:id") will use WidgetsWithIdForm and WidgetsWithIdHandler
  • form("/admin/widgets/:internal_id") will useAdmin::WidgetsWithInternalIdFormandAdmin::WidgetsWithInternalIdHandler`


126
127
128
129
# File 'lib/brut/sinatra_helpers.rb', line 126

def form(path)
  route = Brut.container.routing.register_form(path)
  self.define_handled_route(route, type: :form)
end

#page(path) ⇒ Object

Regsiters a page in your app. A page is what it sounds like - a web page that's rendered from a URL. It will be provided via an HTTP get to the path provided.

The page is rendered dynamically by using an instance of a page class as binding to HTML via ERB. The name of the class and the name of the ERB file are based on the path, according to the conventions described below.

A few examples:

  • page("/widgets") will use WidgetsPage, and expect the HTML in app/src/pages/widgets_page.html.erb
  • page("/widgets/:id") will use WidgetsByIdPage, and expect the HTML in app/src/pages/widgets_by_id_page.html.erb
  • page("/admin/widgets/:internal_id") will useAdmin::WidgetsByInternalIdPage, and expect HTML in app/src/pages/admin/widgets_by_internal_id_page.html.erb`

The general conventions are:

  • Each part of the path that is not a placeholder will be camelized
  • Any part of the path that is a placholder has its leading colon removed, then is camelized, but appended to the previous part with By, thus WidgetsById is created from Widgets, By, and Id.
  • The final part of the path is further appended with Page.
  • These parts now make up a path to a class, so the entire thing is joined by :: to form the fully-qualified class name.

When a GET is issued to the path, the page is instantiated. The page's constructor may accept keyword arguments (however it must not accept any other type of argument).

Each keyword argument found will be provided when the class is created, as follows:

  • Any placeholders, so when a path /widgets/1234 is requested, WidgetsPage.new(id: "1234") will be used to create the page object.
  • Anything in the request context, such as the current user
  • Any query string parameters
  • Anything passed as keyword args to this method, with the following adjustment:
    • Any key ending in _class whose value is a Class will be instantiated and passed in as the key withoutr _class, e.g. form_class: SomeForm will pass form: SomeForm.new to the constructor
  • The flash

Once this page object exists, render will be called to produce HTML to send back to the browser.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/brut/sinatra_helpers.rb', line 65

def page(path)
  Brut.container.routing.register_page(path)

  get path do
    brut_route = Brut.container.routing.for(path: path,method: :get)
    page_class = brut_route.handler_class
    path_template = brut_route.path_template

    root_span = env["brut.otel.root_span"]
    if root_span
      root_span.name = "GET #{path_template}"
      root_span.add_attributes("http.route" => path_template)
    end

    Brut.container.instrumentation.span(page_class.name) do |span|
      span.add_prefixed_attributes("brut", type: :page, class: page_class)
      constructor_args = Brut::FrontEnd::RequestContext.current.as_constructor_args(
        page_class,
        request_params: params,
        route: brut_route,
      )
      span.add_prefixed_attributes("brut.initializer.args", constructor_args.map { |k,v| [k.to_s,v.class.name] }.to_h)
      page_instance = page_class.new(**constructor_args)

      result = page_instance.handle!

      span.add_prefixed_attributes("brut", result_class: result.class)
      case result
      in URI => uri
        redirect to(uri.to_s)
      in Brut::FrontEnd::HttpStatus => http_status
        http_status.to_i
      else
        result
      end
    end
  end
end

#path(path, method:) ⇒ Object

When you need to respond to a given path/method, but it's not a page nor a form. For example, webhooks often require responding to GET even though they aren't rendering pages nor considered to be idempotent.

This will locate a handler class based on the same naming convention as for forms.



146
147
148
149
# File 'lib/brut/sinatra_helpers.rb', line 146

def path(path, method:)
  route = Brut.container.routing.register_path(path, method: Brut::FrontEnd::HttpMethod.new(method))
  self.define_handled_route(route,type: :generic)
end