Custom (Dynamic) Rails Routes via  Database Query
Tags: beginners, rails, ruby
Published: 2020-08-26 03:34:28

For a recent proof-of-concept Rails project, a custom, dynamic, database created URL was required.

**The requirements:** 
1. allow a user to see all mountain biking and hiking trails filtered by state
2. a bookmark-able (for quick access to the particular state) human-readable URL.  

This walkthrough builds off [Rubyonrails.org 3.2 Dynamic Segments ](https://guides.rubyonrails.org/routing.html#dynamic-segments) with more details and support for use.

### Assumptions:
* You have a basic/entry-level working knowledge of Rails routes with `resources` helper method (e.g. `resources :users, only: [:show, edit]`). 
* You only need to `get` and not `update`, `patch`, `post`. 
* You have a database column with data suitable for making a url.
* You read [3.2 Dynamic Segments ](https://guides.rubyonrails.org/routing.html#dynamic-segments) and want a detailed example. 


# Step 1) Create the Route, aka the Dynamic Segments 

Determine what your desired URL structure looks like. 

We needed the url to look like `/trails/state/wa`, `/trails/state/pa`, or `/trails/state/az`.  

The state abbreviation (e.g. WA, AZ, etc) are the url `params[:state ]` (similar to `params[id]`); the database contained consistent and searchable two letter state abbreviations. As such, the route in `routes.rb` is:  

```get "trails/state/:state", to: "trails#index", as: "trail_state"```

**Note: Place this route on the bottom of routes.rb so it does not impact other routes; it is the last route tested.**

The route explained:   
* get `trails/state/wa`, to the `trails#index` action controller to execute the `index.html.erb` code. 
* `trail_state` is the helper path method to create the url (and the query!) for the unique state page. 
*For example, a link to all Washington trails:
```<%= link_to "Washington Trails", trail_state_path("WA")%>```

# Step 2) Query the Database via Your Model
Your exact query will vary but what you query (the query string) is from your url params (in our case `params[:state]`). 

The ActiveRecord query method is set in the `trail.rb model` as such:

```
def self.bystate(state:, amount: "10")
 # default is to pull 10 trails from DB, unless otherwise specified. This is optional. 
 Trail.joins(:park).where(parks: {state: state}).take(amount)
end
```

** Note on the model relationships for the curious: 
* `trail.rb` `belongs_to :park`
* `park.rb` `has_many :trails`. 
* `:state` is a column in the trails table. 
* `:park` is the parks table. 
* `take(10)` pulls the first only the first 10 records for this proof of concept. 

Now we have access to all (errr...10) records based on the query string set by the url. 

# Step 3) Display Data By Custom Query & URL. 
**First**, create your user interface.

Depending on your needs, you could loop through your database to create the `link_to` or you can hardcode. 

For the proof of concept, we hardcoded the states and an “All Trails” button on the trails `index.html.erb` page (below with CSS formatting removed):

```
<div >
    <h3><%= link_to "California Trails", trail_state_path("CA")%><h3>
    <h3><%= link_to "Washington Trails", trail_state_path("WA")%></h3>
    <h3><%= link_to "All Trails", trails_path%><h3>
</div>
```

On-screen a user clicks to see all trails or select state trails. The default index view is all trails via a normal `get trails/` route.

**Second**, determine what to show based on url action.

To determine if we load a custom route and the resulting data query, check if the `params[:state]` is blank.  If false (ie. no `params[wa]`), load all trails. 

```
<%if !params[:state]%>
   <%# if there is no request for trails by state, show all the trails%>
  <% Trail.all.each do |trail|%>
    <%= render partial: "trails/trail", locals: {trail: trail}%>
  <%end%>
<%end%>
```  
Otherwise, `params[:state]` is true and query the database and reload the index page with only selected state trails

```
<% Trail.bystate(state:params[:state]).each do |trail| %>
   <%= render partial: "trails/trail", locals: {trail: trail}%>
<%end%>
```

# The Full Code Snippets 
``` 
routes.rb
Rails.application.routes.draw do
  resources :parks, only: [:index, :show]
  resources :trails, only: [:index, :show]

root to: "homepage#index"

get "trails/state/:state", to: "trails#index", as: "trail_state"

end
```
```
trail.rb model
class Trail < ApplicationRecord
  belongs_to :park
  
def self.bystate(state:, amount: "10")
  Trail.joins(:park).where(parks: {state: state}).take(amount)
end
end
```
```
index.html.rb
<h2 class="font-weight-light text-center mt-5">See Trails by State</h2> 
<div class="row mx-md-n5 mt-5">
    <h3 class="font-weight-light text-center col px-md-3 d-flex justify-content-center"><%= link_to "California Trails", trail_state_path("CA")%><h3>
    <h3 class="font-weight-light text-center col px-md-3"><%= link_to "Washington Trails", trail_state_path("WA")%></h3>
    <h3 class="font-weight-light text-center col px-md-3 d-flex justify-content-center"><%= link_to "All Trails", trails_path%><h3>
</div>

<%# if there is a url request to show by state, show only that state%>
<div class="album">
   <div class="container-fluid text-center">
        <div class="d-flex justify-content-center row">
        <% Trail.bystate(state:params[:state]).each do |trail| %>
           <%= render partial: "trails/trail", locals: {trail: trail}%>
        <%end%>
    </div>
  </div>
</div>

<%if !params[:state]%>
<%# if there is no request for trails by state, show all the trails%>
    <div class="album font-weight-light">
    <div class="container-fluid text-center">
        <div class="d-flex justify-content-center row">
            <% Trail.all.each do |trail|%>
                <%= render partial: "trails/trail", locals: {trail: trail}%>
            <%end%>
            </div>
        </div>
    </div>
<%end%>
```