Brad Beggs
Posted on August 26, 2020
For a recent proof-of-concept Rails project, a custom, dynamic, database created URL was required.
The requirements:
- allow a user to see all mountain biking and hiking trails filtered by state
- a bookmark-able (for quick access to the particular state) human-readable URL.
This walkthrough builds off Rubyonrails.org 3.2 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 notupdate
,patch
,post
. - You have a database column with data suitable for making a url.
- You read 3.2 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 thetrails#index
action controller to execute theindex.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%>
Posted on August 26, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
June 13, 2023