One place for hosting & domains

      How To Add Stimulus to a Ruby on Rails Application


      Introduction

      If you are working with a Ruby on Rails project, your requirements may include some interactivity with the HTML generated by your view templates. If so, you have a few choices for how to implement this interactivity.

      For example, you could implement a JavaScript framework like React or Ember. If your requirements include handling state on the client side, or if you are concerned about performance issues associated with frequent queries to the server, then choosing one of these frameworks may make sense. Many Single Page Applications (SPAs) take this approach.

      However, there are several considerations to keep in mind when implementing a framework that manages state and frequent updates on the client side:

      1. It’s possible for loading and conversion requirements — things like parsing JavaScript, and fetching and converting JSON to HTML — to limit performance.
      2. Commitment to a framework may involve writing more code than your particular use case requires, particularly if you are looking for small-scale JavaScript enhancements.
      3. State managed on both the client and server side can lead to a duplication of efforts, and increases the surface area for errors.

      As an alternative, the team at Basecamp (the same team that wrote Rails) has created Stimulus.js, which they describe as “a modest JavaScript framework for the HTML you already have.” Stimulus is meant to enhance a modern Rails application by working with server-side generated HTML. State lives in the Document Object Model (DOM), and the framework offers standard ways of interacting with elements and events in the DOM. It works side by side with Turbolinks (included in Rails 5+ by default) to improve performance and load times with code that is limited and scoped to a clearly defined purpose.

      In this tutorial, you will install and use Stimulus to build on an existing Rails application that offers readers information about sharks. The application already has a model for handling shark data, but you will add a nested resource for posts about individual sharks, allowing users to build out a body of thoughts and opinions about sharks. This piece runs roughly parallel to How To Create Nested Resources for a Ruby on Rails Application, except that we will be using JavaScript to manipulate the position and appearance of posts on the page. We will also take a slightly different approach to building out the post model itself.

      Prerequisites

      To follow this tutorial, you will need:

      • A local machine or development server running Ubuntu 18.04. Your development machine should have a non-root user with administrative privileges and a firewall configured with ufw. For instructions on how to set this up, see our Initial Server Setup with Ubuntu 18.04 tutorial.
      • Node.js and npm installed on your local machine or development server. This tutorial uses Node.js version 10.16.3 and npm version 6.9.0. For guidance on installing Node.js and npm on Ubuntu 18.04, follow the instructions in the “Installing Using a PPA” section of How To Install Node.js on Ubuntu 18.04.
      • Ruby, rbenv, and Rails installed on your local machine or development server, following Steps 1-4 in How To Install Ruby on Rails with rbenv on Ubuntu 18.04. This tutorial uses Ruby 2.5.1, rbenv 1.1.2, and Rails 5.2.3.
      • SQLite installed, and a basic shark information application created, following the directions in How To Build a Ruby on Rails Application.

      Step 1 — Creating a Nested Model

      Our first step will be to create a nested Post model, which we will associate with our existing Shark model. We will do this by creating Active Record associations between our models: posts will belong to particular sharks, and each shark can have multiple posts.

      To get started, navigate to the sharkapp directory that you created for your Rails project in the prerequisites:

      To create our Post model, we’ll use the rails generate command with the model generator. Type the following command to create the model:

      • rails generate model Post body:text shark:references

      With body:text, we’re telling Rails to include a body field in the posts database table — the table that maps to the Post model. We’re also including the :references keyword, which sets up an association between the Shark and Post models. Specifically, this will ensure that a foreign key representing each shark entry in the sharks database is added to the posts database.

      Once you have run the command, you will see output confirming the resources that Rails has generated for the application. Before moving on, you can check your database migration file to look at the relationship that now exists between your models and database tables. Use the following command to look at the contents of the file, making sure to substitute the timestamp on your own migration file for what’s shown here:

      • cat db/migrate/20190805132506_create_posts.rb

      You will see the following output:

      Output

      class CreatePosts < ActiveRecord::Migration[5.2] def change create_table :posts do |t| t.text :body t.references :shark, foreign_key: true t.timestamps end end end

      As you can see, the table includes a column for a shark foreign key. This key will take the form of model_name_id — in our case, shark_id.

      Rails has established the relationship between the models elsewhere as well. Take a look at the newly generated Post model with the following command:

      Output

      class Post < ApplicationRecord belongs_to :shark end

      The belongs_to association sets up a relationship between models in which a single instance of the declaring model belongs to a single instance of the named model. In the case of our application, this means that a single post belongs to a single shark.

      Though Rails has already set the belongs_to association in our Post model, we will need to specify a has_many association in our Shark model as well in order for that relationship to function properly.

      To add the has_many association to the Shark model, open app/models/shark.rb using nano or your favorite editor:

      Add the following line to the file to establish the relationship between sharks and posts:

      ~/sharkapp/app/models/shark.rb

      class Shark < ApplicationRecord
        has_many :posts
        validates :name, presence: true, uniqueness: true
        validates :facts, presence: true
      end
      

      One thing that is worth thinking about here is what happens to posts once a particular shark is deleted. We likely do not want the posts associated with a deleted shark persisting in the database. To ensure that any posts associated with a given shark are eliminated when that shark is deleted, we can include the dependent option with the association.

      Add the following code to the file to ensure that the destroy action on a given shark deletes any associated posts:

      ~/sharkapp/app/models/shark.rb

      class Shark < ApplicationRecord
        has_many :posts , dependent: :destroy
        validates :name, presence: true, uniqueness: true
        validates :facts, presence: true
      end
      

      Once you have finished making these changes, save and close the file. If you are working with nano, do this by pressing CTRL+X, Y, then ENTER.

      You now have a model generated for your posts, but you will also need a controller to coordinate between the data in your database and the HTML that’s generated and presented to users.

      Step 2 — Creating a Controller for a Nested Resource

      Creating a posts controller will involve setting a nested resource route in the application’s main routing file and creating the controller file itself to specify the methods we want associated with particular actions.

      To begin, open your config/routes.rb file to establish the relationship between your resourceful routes:

      Currently, the file looks like this:

      ~/sharkapp/config/routes.rb

      Rails.application.routes.draw do
        resources :sharks
      
        root 'sharks#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      We want to create a dependent relationship relationship between shark and post resources. To do this, update your route declaration to make :sharks the parent of :posts. Update the code in the file to look like the following:

      ~/sharkapp/config/routes.rb

      Rails.application.routes.draw do
        resources :sharks do
          resources :posts
        end
        root 'sharks#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      Save and close the file when you are finished editing.

      Next, create a new file called app/controllers/posts_controller.rb for the controller:

      • nano app/controllers/posts_controller.rb

      In this file, we’ll define the methods that we will use to create and destroy individual posts. However, because this is a nested model, we’ll also want to create a local instance variable, @shark, that we can use to associate particular posts with specific sharks.

      First, we can create the PostsController class itself, along with two private methods: get_shark, which will allow us to reference a particular shark, and post_params, which gives us access to user-submitted information by way of the params method.

      Add the following code to the file:

      ~/sharkapp/app/controllers/posts_controller.rb

      class PostsController < ApplicationController
        before_action :get_shark 
      
        private
      
        def get_shark
          @shark = Shark.find(params[:shark_id])
        end
      
        def post_params
          params.require(:post).permit(:body, :shark_id)
        end
      end
      

      You now have methods to get the particular shark instances with which your posts will be associated, using the :shark_id key, and the data that users are inputting to create posts. Both of these objects will now be available for the methods you will define to handle creating and destroying posts.

      Next, above the private methods, add the following code to the file to define your create and destroy methods:

      ~/sharkapp/app/controllers/posts_controller.rb

      . . .
        def create
          @post = @shark.posts.create(post_params)
        end
      
        def destroy
          @post = @shark.posts.find(params[:id])
          @post.destroy   
        end
      . . .
      

      These methods associate @post instances with particular @shark instances, and use the collection methods that became available to us when we created the has_many association between sharks and posts. Methods such as find and create allow us to target the collection of posts associated with a particular shark.

      The finished file will look like this:

      ~/sharkapp/app/controllers/posts_controller.rb

      class PostsController < ApplicationController
        before_action :get_shark 
      
        def create
          @post = @shark.posts.create(post_params)
        end
      
        def destroy
          @post = @shark.posts.find(params[:id])
          @post.destroy   
        end
      
        private
      
        def get_shark
          @shark = Shark.find(params[:shark_id])
        end
      
        def post_params
          params.require(:post).permit(:body, :shark_id)
        end
      end
      

      Save and close the file when you are finished editing.

      With your controller and model in place, you can begin thinking about your view templates and how you will organize your application’s generated HTML.

      Step 3 — Reorganizing Views with Partials

      You have created a Post model and controller, so the last thing to think about from a Rails perspective will be the views that present and allow users to input information about sharks. Views are also the place where you will have a chance to build out interactivity with Stimulus.

      In this step, you will map out your views and partials, which will be the starting point for your work with Stimulus.

      The view that will act as the base for posts and all partials associated with posts is the sharks/show view.

      Open the file:

      • nano app/views/sharks/show.html.erb

      Currently, the file looks like this:

      ~/sharkapp/app/views/sharks/show.html.erb

      <p id="notice"><%= notice %></p>
      
      <p>
        <strong>Name:</strong>
        <%= @shark.name %>
      </p>
      
      <p>
        <strong>Facts:</strong>
        <%= @shark.facts %>
      </p>
      
      <%= link_to 'Edit', edit_shark_path(@shark) %> |
      <%= link_to 'Back', sharks_path %>
      

      When we created our Post model, we opted not to generate views for our posts, since we will handle them through our sharks/show view. So in this view, the first thing we will address is how we will accept user input for new posts, and how we will present posts back to the user.

      Note: For an alternative to this approach, please see How To Create Nested Resources for a Ruby on Rails Application, which sets up post views using the full range of Create, Read, Update, Delete (CRUD) methods defined in the posts controller. For a discussion of these methods and how they work, please see Step 3 of How To Build a Ruby on Rails Application.

      Instead of building all of our functionality into this view, we will use partials — reusable templates that serve a particular function. We will create one partial for new posts, and another to control how posts are displayed back to the user. Throughout, we’ll be thinking about how and where we can use Stimulus to manipulate the appearance of posts on the page, since our goal is to control the presentation of posts with JavaScript.

      First, below shark facts, add an <h2> header for posts and a line to render a partial called sharks/posts:

      ~/sharkapp/app/views/sharks/show.html.erb

      . . . 
      <p>
        <strong>Facts:</strong>
        <%= @shark.facts %>
      </p>
      
      <h2>Posts</h2>
      <%= render 'sharks/posts' %>
      . . . 
      

      This will render the partial with the form builder for new post objects.

      Next, below the Edit and Back links, we will add a section to control the presentation of older posts on the page. Add the following lines to the file to render a partial called sharks/all:

      ~/sharkapp/app/views/sharks/show.html.erb

      <%= link_to 'Edit', edit_shark_path(@shark) %> |
      <%= link_to 'Back', sharks_path %>
      
      <div>
        <%= render 'sharks/all' %>
      </div>
      

      The <div> element will be useful when we start integrating Stimulus into this file.

      Once you are finished making these edits, save and close the file. With the changes you’ve made on the Rails side, you can now move on to installing and integrating Stimulus into your application.

      Step 4 — Installing Stimulus

      The first step in using Stimulus will be to install and configure our application to work with it. This will include making sure we have the correct dependencies, including the Yarn package manager and Webpacker, the gem that will allow us to work with the JavaScript pre-processor and bundler webpack. With these dependencies in place, we will be able to install Stimulus and use JavaScript to manipulate events and elements in the DOM.

      Let’s begin by installing Yarn. First, update your package list:

      Next, add the GPG key for the Debian Yarn repository:

      • curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -

      Add the repository to your APT sources:

      • echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list

      Update the package database with the newly added Yarn packages:

      And finally, install Yarn:

      With yarn installed, you can move on to adding the webpacker gem to your project.

      Open your project’s Gemfile, which lists the gem dependencies for your project:

      Inside the file, you will see Turbolinks enabled by default:

      ~/sharkapp/Gemfile

      . . . 
      # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
      gem 'turbolinks', '~> 5'
      . . . 
      

      Turbolinks is designed to improve performance by optimizing page loads: instead of having link clicks navigate to a new page, Turbolinks intercepts these click events and makes the page request using Asynchronous JavaScript and HTML (AJAX). It then replaces the body of the current page and merges the contents of the <head> sections, while the JavaScript window and document objects and the <html> element persist between renders. This addresses one of the main causes of slow page load times: the reloading of CSS and JavaScript resources.

      We get Turbolinks by default in our Gemfile, but we will need to add the webpacker gem so that we can install and use Stimulus. Below the turbolinks gem, add webpacker:

      ~/sharkapp/Gemfile

      . . . 
      # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
      gem 'turbolinks', '~> 5'
      gem 'webpacker', '~> 4.x'
      . . . 
      

      Save and close the file when you are finished.

      Next, add the gem to your project’s bundle with the bundle command:

      This will generate a new Gemfile.lock file — the definitive record of gems and versions for your project.

      Next, install the gem in the context of your bundle with the following bundle exec command:

      • bundle exec rails webpacker:install

      Once the installation is complete, we will need to make one small adjustment to our application’s content security file. This is due to the fact that we are working with Rails 5.2+, which is a Content Security Policy (CSP) restricted environment, meaning that the only scripts allowed in the application must be from trusted sources.

      Open config/initializers/content_security_policy.rb, which is the default file Rails gives us for defining application-wide security policies:

      • nano config/initializers/content_security_policy.rb

      Add the following lines to the bottom of the file to allow webpack-dev-server — the server that serves our application’s webpack bundle — as an allowed origin:

      ~/sharkapp/config/initializers/content_security_policy.rb

      . . . 
      Rails.application.config.content_security_policy do |policy|
        policy.connect_src :self, :https, 'http://localhost:3035', 'ws://localhost:3035' if Rails.env.development?
      end
      

      This will ensure that the webpacker-dev-server is recognized as a trusted asset source.

      Save and close the file when you are finished making this change.

      By installing webpacker, you created two new directories in your project’s app directory, the directory where your main application code is located. The new parent directory, app/javascript, will be where your project’s JavaScript code will live, and it will have the following structure:

      Output

      ├── javascript │ ├── controllers │ │ ├── hello_controller.js │ │ └── index.js │ └── packs │ └── application.js

      The app/javascript directory will contain two child directories: app/javascript/packs, which will have your webpack entry points, and app/javascript/controllers, where you will define your Stimulus controllers. The bundle exec command that we just used will create the app/javascript/packs directory, but we will need to install Stimulus for the app/javascript/controllers directory to be autogenerated.

      With webpacker installed, we can now install Stimulus with the following command:

      • bundle exec rails webpacker:install:stimulus

      You will see output like the following, indicating that the installation was successful:

      Output

      . . . success Saved lockfile. success Saved 5 new dependencies. info Direct dependencies └─ stimulus@1.1.1 info All dependencies ├─ @stimulus/core@1.1.1 ├─ @stimulus/multimap@1.1.1 ├─ @stimulus/mutation-observers@1.1.1 ├─ @stimulus/webpack-helpers@1.1.1 └─ stimulus@1.1.1 Done in 8.30s. Webpacker now supports Stimulus.js 🎉

      We now have Stimulus installed, and the main directories we need to work with it in place. Before moving on to writing any code, we’ll need to make a few application-level adjustments to complete the installation process.

      First, we’ll need to make an adjustment to app/views/layouts/application.html.erb to ensure that our JavaScript code is available and that the code defined in our main webpacker entry point, app/javascript/packs/application.js, runs each time a page is loaded.

      Open that file:

      • nano app/views/layouts/application.html.erb

      Change the following javascript_include_tag tag to javascript_pack_tag to load app/javascript/packs/application.js:

      ~/sharkapp/app/views/layouts/application.html.erb

      . . .
          <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
          <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
      . . . 
      

      Save and close the file when you have made this change.

      Next, open app/javascript/packs/application.js:

      • nano app/javascript/packs/application.js

      Initially, the file will look like this:

      ~/sharkapp/app/javascript/packs/application.js

      . . . 
      console.log('Hello World from Webpacker')
      
      import "controllers"
      

      Delete the boilerplate code that’s there, and add the following code to load your Stimulus controller files and boot the application instance:

      ~/sharkapp/app/javascript/packs/application.js

      . . . 
      import { Application } from "stimulus"
      import { definitionsFromContext } from "stimulus/webpack-helpers"
      
      const application = Application.start()
      const context = require.context("../controllers", true, /.js$/)
      application.load(definitionsFromContext(context))
      

      This code uses webpack helper methods to require the controllers in the app/javascript/controllers directory and load this context for use in your application.

      Save and close the file when you are finished editing.

      You now have Stimulus installed and ready to use in your application. Next, we’ll build out the partials that we referenced in our sharks show view — sharks/posts and sharks/all — using Stimulus controllers, targets, and actions.

      Step 5 — Using Stimulus in Rails Partials

      Our sharks/posts partial will use the form_with form helper to create a new post object. It will also make use of Stimulus’s three core concepts: controllers, targets, and actions. These concepts work as follows:

      • Controllers are JavaScript classes that are defined in JavaScript modules and exported as the module’s default object. Through controllers, you have access to particular HTML elements and the Stimulus Application instance defined in app/javascript/packs/application.js.
      • Targets allow you to reference particular HTML elements by name, and are associated with particular controllers.
      • Actions control how DOM events are handled by controllers, and are also associated with particular controllers. They create a connection between the HTML element associated with the controller, the methods defined in the controller, and a DOM event listener.

      In our partial, we’re first going to build a form as we normally would using Rails. We will then add a Stimulus controller, action, and targets to the form in order to use JavaScript to control how new posts get added to the page.

      First, create a new file for the partial:

      • nano app/views/sharks/_posts.html.erb

      Inside the file, add the following code to create a new post object using the form_with helper:

      ~/sharkapp/app/views/sharks/_posts.html.erb

              <%= form_with model: [@shark, @shark.posts.build] do |form| %>
                      <%= form.text_area :body, placeholder: "Your post here" %>
                      <br>
                      <%= form.submit %>
              <% end %>
      

      So far, this form behaves like a typical Rails form, using the form_with helper to build a post object with the fields defined for the Post model. Thus, the form has a field for the post :body, to which we’ve added a placeholder with a prompt for filling in a post.

      Additionally, the form is scoped to take advantage of the collection methods that come with the associations between the Shark and Post models. In this case, the new post object that’s created from user-submitted data will belong to the collection of posts associated with the shark we’re currently viewing.

      Our goal now is to add some Stimulus controllers, events, and actions to control how the post data gets displayed on the page. The user will ultimately submit post data and see it posted to the page thanks to a Stimulus action.

      First, we’ll add a controller to the form called posts in a <div> element:

      ~/sharkapp/app/views/sharks/_posts.html.erb

      <div data-controller="posts">
              <%= form_with model: [@shark, @shark.posts.build] do |form| %>
                       <%= form.text_area :body, placeholder: "Your post here" %>
                       <br>
                       <%= form.submit %>
              <% end %>
      </div>
      

      Make sure you add the closing <div> tag to scope the controller properly.

      Next, we’ll attach an action to the form that will be triggered by the form submit event. This action will control how user input is displayed on the page. It will reference an addPost method that we will define in the posts Stimulus controller:

      ~/sharkapp/app/views/sharks/_posts.html.erb

      <div data-controller="posts">
              <%= form_with model: [@shark, @shark.posts.build], data: { action: "posts#addBody" } do |form| %>
              . . . 
                       <%= form.submit %>
              <% end %>
      </div>
      

      We use the :data option with form_with to submit the Stimulus action as an additional HTML data attribute. The action itself has a value called an action descriptor made up of the following:

      • The DOM event to listen for. Here, we are using the default event associated with form elements, submit, so we do not need to specify the event in the descriptor itself. For more information about common element/event pairs, see the Stimulus documentation.
      • The controller identifier, in our case posts.
      • The method that the event should invoke. In our case, this is the addBody method that we will define in the controller.

      Next, we’ll attach a data target to the user input defined in the :body <textarea> element, since we will use this inputted value in the addBody method.

      Add the following :data option to the :body <textarea> element:

      ~/sharkapp/app/views/sharks/_posts.html.erb

      <div data-controller="posts">
              <%= form_with model: [@shark, @shark.posts.build], data: { action: "posts#addBody" } do |form| %>
                      <%= form.text_area :body, placeholder: "Your post here", data: { target: "posts.body" } %>
      . . .
      

      Much like action descriptors, Stimulus targets have target descriptors, which include the controller identifier and the target name. In this case, posts is our controller, and body is the target itself.

      As a last step, we’ll add a data target for the inputted body values so that users will be able to see their posts as soon as they are submitted.

      Add the following <ul> element with an add target below the form and above the closing <div>:

      ~/sharkapp/app/views/sharks/_posts.html.erb

      . . .
              <% end %>
        <ul data-target="posts.add">
        </ul>
      
      </div>
      

      As with the body target, our target descriptor includes both the name of the controller and the target — in this case, add.

      The finished partial will look like this:

      ~/sharkapp/app/views/sharks/_posts.html.erb

      <div data-controller="posts">
              <%= form_with model: [@shark, @shark.posts.build], data: { action: "posts#addBody"} do |form| %>
                      <%= form.text_area :body, placeholder: "Your post here", data: { target: "posts.body" } %>
                      <br>
                      <%= form.submit %>
              <% end %>
        <ul data-target="posts.add">
        </ul>
      
      </div>
      

      Once you have made these changes, you can save and close the file.

      You have now created one of the two partials you added to the sharks/show view template. Next, you’ll create the second, sharks/all, which will show all of the older posts from the database.

      Create a new file named _all.html.erb in the app/views/sharks/ directory:

      • nano app/views/sharks/_all.html.erb

      Add the following code to the file to iterate through the collection of posts associated with the selected shark:

      ~/sharkapp/app/views/sharks/_all.html.erb

      <% for post in @shark.posts  %>
          <ul>
      
              <li class="post">
                  <%= post.body %>
              </li>
      
          </ul>
          <% end %>
      

      This code uses a for loop to iterate through each post instance in the collection of post objects associated with a particular shark.

      We can now add some Stimulus actions to this partial to control the appearance of posts on the page. Specifically, we will add actions that will control upvotes and whether or not posts are visible on the page

      Before we do that, however, we will need to add a gem to our project so that we can work with Font Awesome icons, which we’ll use to register upvotes. Open a second terminal window, and navigate to your sharkapp project directory.

      Open your Gemfile:

      Below your webpacker gem, add the following line to include the font-awesome-rails gem in the project:

      ~/sharkapp/Gemfile

      . . . 
      gem 'webpacker', '~> 4.x'
      gem 'font-awesome-rails', '~>4.x'
      . . . 
      

      Save and close the file.

      Next, install the gem:

      Finally, open your application’s main stylesheet, app/assets/stylesheets/application.css:

      • nano app/assets/stylesheets/application.css

      Add the following line to include Font Awesome’s styles in your project:

      ~/sharkapp/app/assets/stylesheets/application.css

      . . . 
      *
       *= require_tree .
       *= require_self
       *= require font-awesome
       */
      

      Save and close the file. You can now close your second terminal window.

      Back in your app/views/sharks/_all.html.erb partial, you can now add two button_tags with associated Stimulus actions, which will be triggered on click events. One button will give users the option to upvote a post and the other will give them the option to remove it from the page view.

      Add the following code to app/views/sharks/_all.html.erb:

      ~/sharkapp/app/views/sharks/_all.html.erb

      <% for post in @shark.posts  %>
          <ul>
      
              <li class="post">
                  <%= post.body %>
                  <%= button_tag "Remove Post", data: { controller: "posts", action: "posts#remove" } %>
                  <%= button_tag "Upvote Post", data: { controller: "posts", action: "posts#upvote" } %>
              </li>
      
          </ul>
          <% end %>
      

      Button tags also take a :data option, so we’ve added our posts Stimulus controller and two actions: remove and upvote. Once again, in the action descriptors, we only need to define our controller and method, since the default event associated with button elements is click. Clicking on each of these buttons will trigger the respective remove and upvote methods defined in our controller.

      Save and close the file when you have finished editing.

      The final change we will make before moving on to defining our controller is to set a data target and action to control how and when the sharks/all partial will be displayed.

      Open the show template again, where the initial call to render sharks/all is currently defined:

      • nano app/views/sharks/show.html.erb

      At the bottom of the file, we have a <div> element that currently looks like this:

      ~/sharkapp/app/views/sharks/show.html.erb

      . . . 
      <div>
        <%= render 'sharks/all' %>
      </div>
      

      First, add a controller to this <div> element to scope actions and targets:

      ~/sharkapp/app/views/sharks/show.html.erb

      . . . 
      <div data-controller="posts">
        <%= render 'sharks/all' %>
      </div>
      

      Next, add a button to control the appearance of the partial on the page. This button will trigger a showAll method in our posts controller.

      Add the button below the <div> element and above the render statement:

      ~/sharkapp/app/views/sharks/show.html.erb

      . . . 
      <div data-controller="posts">
      
      <button data-action="posts#showAll">Show Older Posts</button>
      
        <%= render 'sharks/all' %>
      

      Again, we only need to identify our posts controller and showAll method here — the action will be triggered by a click event.

      Next, we will add a data target. The goal of setting this target is to control the appearance of the partial on the page. Ultimately, we want users to see older posts only if they have opted into doing so by clicking on the Show Older Posts button.

      We will therefore attach a data target called show to the sharks/all partial, and set its default style to visibility:hidden. This will hide the partial unless users opt in to seeing it by clicking on the button.

      Add the following <div> element with the show target and style definition below the button and above the partial render statement:

      ~/sharkapp/app/views/sharks/show.html.erb

      . . . 
      <div data-controller="posts">
      
      <button data-action="posts#showAll">Show Older Posts</button>
      
      <div data-target="posts.show" style="visibility:hidden">
        <%= render 'sharks/all' %>
      </div>
      

      Be sure to add the closing </div> tag.

      The finished show template will look like this:

      ~/sharkapp/app/views/sharks/show.html.erb

      <p id="notice"><%= notice %></p>
      
      <p>
        <strong>Name:</strong>
        <%= @shark.name %>
      </p>
      
      <p>
        <strong>Facts:</strong>
        <%= @shark.facts %>
      </p>
      
      <h2>Posts</h2>
      
      <%= render 'sharks/posts' %>
      
      <%= link_to 'Edit', edit_shark_path(@shark) %> |
      <%= link_to 'Back', sharks_path %>
      
      <div data-controller="posts">
      
      <button data-action="posts#showAll">Show Older Posts</button>
      
      <div data-target="posts.show" style="visibility:hidden">
        <%= render 'sharks/all' %>
      </div>
      </div>
      

      Save and close the file when you are finished editing.

      With this template and its associated partials finished, you can move on to creating the controller with the methods you’ve referenced in these files.

      Step 6 — Creating the Stimulus Controller

      Installing Stimulus created the app/javascript/controllers directory, which is where webpack is loading our application context from, so we will create our posts controller in this directory. This controller will include each of the methods we referenced in the previous step:

      • addBody(), to add new posts.
      • showAll(), to show older posts.
      • remove(), to remove posts from the current view.
      • upvote(), to attach an upvote icon to posts.

      Create a file called posts_controller.js in the app/javascript/controllers directory:

      • nano app/javascript/controllers/posts_controller.js

      First, at the top of the file, extend Stimulus’s built-in Controller class:

      ~/sharkapp/app/javascript/controllers/posts_controller.js

      import { Controller } from "stimulus"
      
      export default class extends Controller {
      }
      

      Next, add the following target definitions to the file:

      ~/sharkapp/app/javascript/controllers/posts_controller.js

      . . .
      export default class extends Controller {
          static targets = ["body", "add", "show"]
      }
      

      Defining targets in this way will allow us to access them in our methods with the this.target-nameTarget property, which gives us the first matching target element. So, for example, to match the body data target defined in our targets array, we would use this.bodyTarget. This property allows us to manipulate things like input values or css styles.

      Next, we can define the addBody method, which will control the appearance of new posts on the page. Add the following code below the target definitions to define this method:

      ~/sharkapp/app/javascript/controllers/posts_controller.js

      . . .
      export default class extends Controller {
          static targets = [ "body", "add", "show"]
      
          addBody() {
              let content = this.bodyTarget.value;
              this.addTarget.insertAdjacentHTML('beforebegin', "<li>" + content + "</li>");
          }
      }
      

      This method defines a content variable with the let keyword and sets it equal to the post input string that users entered into the posts form. It does this by virtue of the body data target that we attached to the <textarea> element in our form. Using this.bodyTarget to match this element, we can then use the value property that is associated with that element to set the value of content as the post input users have entered.

      Next, the method adds this post input to the add target we added to the <ul> element below the form builder in the sharks/posts partial. It does this using the Element.insertAdjacentHTML() method, which will insert the content of the new post, set in the content variable, before the add target element. We’ve also enclosed the new post in an <li> element, so that new posts appear as bulleted list items.

      Next, below the addBody method, we can add the showAll method, which will control the appearance of older posts on the page:

      ~/sharkapp/app/javascript/controllers/posts_controller.js

      . . . 
      export default class extends Controller {
      . . .
          addBody() {
              let content = this.bodyTarget.value;
              this.addTarget.insertAdjacentHTML('beforebegin', "<li>" + content + "</li>");
          }
      
          showAll() {
              this.showTarget.style.visibility = "visible";
          }
      
      }
      

      Here, we again use the this.target-nameTarget property to match our show target, which is attached to the <div> element with the sharks/all partial. We gave it a default style, "visibility:hidden", so in this method, we simply change the style to "visible". This will show the partial to users who have opted into seeing older posts.

      Below showAll, we’ll next add an upvote method, to allow users to “upvote” posts on the page by attaching the free Font Awesome check-circle icon to a particular post.

      Add the following code to define this method:

      ~/sharkapp/app/javascript/controllers/posts_controller.js

      . . . 
      export default class extends Controller {
      . . . 
      
          showAll() {
              this.showTarget.style.visibility = "visible";
          }
      
          upvote() {
              let post = event.target.closest(".post");
              post.insertAdjacentHTML('beforeend', '<i class="fa fa-check-circle"></i>');
          }
      
      }
      

      Here, we’re creating a post variable that will target the closest <li> element with the class post — the class we attached to each <li> element in our loop iteration in sharks/all. This will target the closest post and add the check-circle icon just inside <li> element, after its last child.

      Next, we’ll use a similar method to hide posts on the page. Add the following code below the upvote method to define a remove method:

      ~/sharkapp/app/javascript/controllers/posts_controller.js

      . . . 
      export default class extends Controller {
      . . . 
      
          upvote() {
              let post = event.target.closest(".post");
              post.insertAdjacentHTML('beforeend', '<i class="fa fa-check-circle"></i>');
          }
      
          remove() {
              let post = event.target.closest(".post");
              post.style.visibility = "hidden";
          }
      }
      

      Once again, our post variable will target the closest <li> element with the class post. It will then set the visibility property to "hidden" to hide the post on the page.

      The finished controller file will now look like this:

      ~/sharkapp/app/javascript/controllers/posts_controller.js

      import { Controller } from "stimulus"
      
      export default class extends Controller {
      
          static targets = ["body", "add", "show"]
      
          addBody() {
              let content = this.bodyTarget.value;
              this.addTarget.insertAdjacentHTML('beforebegin', "<li>" + content + "</li>");
          }
      
          showAll() {
              this.showTarget.style.visibility = "visible";
          }
      
          upvote() {
              let post = event.target.closest(".post");
              post.insertAdjacentHTML('beforeend', '<i class="fa fa-check-circle"></i>');
          }
      
          remove() {
              let post = event.target.closest(".post");
              post.style.visibility = "hidden";
          }
      } 
      

      Save and close the file when you are finished editing.

      With your Stimulus controller in place, you can move on to making some final changes to the index view and testing your application.

      Step 7 — Modifying the Index View and Testing the Application

      With one final change to the sharks index view you will be ready to test out your application. The index view is the root of the application, which you set in Step 4 of How To Build a Ruby on Rails Application.

      Open the file:

      • nano app/views/sharks/index.html.erb

      In place of the link_to helpers that were autogenerated for us to display and destroy sharks, we’ll use button_to helpers. This will help us work with generated HTML code instead of the default Rails JavaScript assets, which we specified we would no longer use in Step 1, when we changed javascript_include_tag to javascript_pack_tag in app/views/layouts/application.html.erb.

      Replace the existing link_to helpers in the file with the following button_to helpers:

      ~/sharkapp/app/views/sharks/index.html.erb

      . . . 
        <tbody>
          <% @sharks.each do |shark| %>
            <tr>
              <td><%= shark.name %></td>
              <td><%= shark.facts %></td>
              <td><%= button_to 'Show', shark_path(:id => shark.id), :method => :get %></td>
              <td><%= button_to 'Edit', edit_shark_path(:id => shark.id), :method => :get %></td>
              <td><%= button_to 'Destroy', shark_path(:id => shark.id), :method => :delete %></td>
            </tr>
          <% end %>
        </tbody>
      . . . 
      

      These helpers accomplish much the same things as their link_to counterparts, but the Destroy helper now relies on generated HTML rather than Rails’s default JavaScript.

      Save and close the file when you are finished editing.

      You are now ready to test your application.

      First, run your database migrations:

      Next, start your server. If you are working locally, you can do this with the following command:

      If you are working on a development server, you can start the application with:

      • rails s --binding=your_server_ip

      Navigate to the application landing page in your browser. If you are working locally, this will be localhost:3000, or http://your_server_ip:3000 if you are working on a server.

      You will see the following landing page:

      Application Landing Page

      Clicking on Show will take you to the show view for this shark. Here you will see a form to fill out a post:

      Shark Show Page

      In the post form, type “These sharks are scary!”:

      Filled in Post

      Click on Create Post. You will now see the new post on the page:

      New Post Added to Page

      You can add another new post, if you would like. This time, type “These sharks are often misrepresented in films” and click Create Post:

      Second Post Added to Page

      In order to test the functionality of the Show Older Posts feature, we will need to leave this page, since our Great White does not currently have any posts that are older than the ones we’ve just added.

      Click Back to get to the main page, and then Show to return to the Great White landing page:

      Shark Show Page

      Clicking on Show Older Posts will now show you the posts you created:

      Show Older Posts

      You can now upvote a post by clicking on the Upvote Post button:

      Upvote a Post

      Similarly, clicking Remove Post will hide the post:

      Remove a Post

      You have now confirmed that you have a working Rails application that uses Stimulus to control how nested post resources are displayed on individual shark pages. You can use this as the jumping off point for future development and experimentation with Stimulus.

      Conclusion

      Stimulus represents a possible alternative to working with rails-ujs, JQuery, and frameworks like React and Vue.

      As discussed in the introduction, Stimulus makes the most sense when you need to work directly with HTML generated by the server. It is lightweight, and aims to make code – particularly HTML – self-explanatory to the highest degree possible. If you don’t need to manage state on the client side, then Stimulus may be a good choice.

      If you are interested in how to create nested resources without a Stimulus integration, you can consult How To Create Nested Resources for a Ruby on Rails Application.

      For more information on how you would integrate React with a Rails application, see How To Set Up a Ruby on Rails Project with a React Frontend.



      Source link

      How To Create Nested Resources for a Ruby on Rails Application


      Introduction

      Ruby on Rails is a web application framework written in Ruby that offers developers an opinionated approach to application development. Working with Rails gives developers:

      • Conventions for handling things like routing, stateful data, and asset management.
      • A firm grounding in the model-view-controller (MCV) architectural pattern, which separates an application’s logic, located in models, from the presentation and routing of application information.

      As you add complexity to your Rails applications, you will likely work with multiple models, which represent your application’s business logic and interface with your database. Adding related models means establishing meaningful relationships between them, which then affect how information gets relayed through your application’s controllers, and how it is captured and presented back to users through views.

      In this tutorial, you will build on an existing Rails application that offers users facts about sharks. This application already has a model for handling shark data, but you will add a nested resource for posts about individual sharks. This will allow users to build out a wider body of thoughts and opinions about individual sharks.

      Prerequisites

      To follow this tutorial, you will need:

      • A local machine or development server running Ubuntu 18.04. Your development machine should have a non-root user with administrative privileges and a firewall configured with ufw. For instructions on how to set this up, see our Initial Server Setup with Ubuntu 18.04 tutorial.
      • Node.js and npm installed on your local machine or development server. This tutorial uses Node.js version 10.16.3 and npm version 6.9.0. For guidance on installing Node.js and npm on Ubuntu 18.04, follow the instructions in the “Installing Using a PPA” section of How To Install Node.js on Ubuntu 18.04.
      • Ruby, rbenv, and Rails installed on your local machine or development server, following Steps 1-4 in How To Install Ruby on Rails with rbenv on Ubuntu 18.04. This tutorial uses Ruby 2.5.1, rbenv 1.1.2, and Rails 5.2.3.
      • SQLite installed, and a basic shark information application created, following the directions in How To Build a Ruby on Rails Application.

      Step 1 — Scaffolding the Nested Model

      Our application will take advantage of Active Record associations to build out a relationship between Shark and Post models: posts will belong to particular sharks, and each shark can have multiple posts. Our Shark and Post models will therefore be related through belongs_to and has_many associations.

      The first step to building out the application in this way will be to create a Post model and related resources. To do this, we can use the rails generate scaffold command, which will give us a model, a database migration to alter the database schema, a controller, a full set of views to manage standard Create, Read, Update, and Delete (CRUD) operations, and templates for partials, helpers, and tests. We will need to modify these resources, but using the scaffold command will save us some time and energy since it generates a structure we can use as a starting point.

      First, make sure that you are in the sharkapp directory for the Rails project that you created in the prerequisites:

      Create your Post resources with the following command:

      • rails generate scaffold Post body:text shark:references

      With body:text, we’re telling Rails to include a body field in the posts database table — the table that maps to the Post model. We’re also including the :references keyword, which sets up an association between the Shark and Post models. Specifically, this will ensure that a foreign key representing each shark entry in the sharks database is added to the posts database.

      Once you have run the command, you will see output confirming the resources that Rails has generated for the application. Before moving on, you can check your database migration file to look at the relationship that now exists between your models and database tables. Use the following command to look at the contents of the file, making sure to substitute the timestamp on your own migration file for what’s shown here:

      • cat db/migrate/20190805132506_create_posts.rb

      You will see the following output:

      Output

      class CreatePosts < ActiveRecord::Migration[5.2] def change create_table :posts do |t| t.text :body t.references :shark, foreign_key: true t.timestamps end end end

      As you can see, the table includes a column for a shark foreign key. This key will take the form of model_name_id — in our case, shark_id.

      Rails has established the relationship between the models elsewhere as well. Take a look at the newly generated Post model with the following command:

      Output

      class Post < ApplicationRecord belongs_to :shark end

      The belongs_to association sets up a relationship between models in which a single instance of the declaring model belongs to a single instance of the named model. In the case of our application, this means that a single post belongs to a single shark.

      In addition to setting this relationship, the rails generate scaffold command also created routes and views for posts, as it did for our shark resources in Step 3 of How To Build a Ruby on Rails Application.

      This is a useful start, but we will need to configure some additional routing and solidify the Active Record association for the Shark model in order for the relationship between our models and routes to work as desired.

      Step 2 — Specifying Nested Routes and Associations for the Parent Model

      Rails has already set the belongs_to association in our Post model, thanks to the :references keyword in the rails generate scaffold command, but in order for that relationship to function properly we will need to specify a has_many association in our Shark model as well. We will also need to make changes to the default routing that Rails gave us in order to make post resources the children of shark resources.

      To add the has_many association to the Shark model, open app/models/shark.rb using nano or your favorite editor:

      Add the following line to the file to establish the relationship between sharks and posts:

      ~/sharkapp/app/models/shark.rb

      class Shark < ApplicationRecord
        has_many :posts
        validates :name, presence: true, uniqueness: true
        validates :facts, presence: true
      end
      

      One thing that is worth thinking about here is what happens to posts once a particular shark is deleted. We likely do not want the posts associated with a deleted shark persisting in the database. To ensure that any posts associated with a given shark are eliminated when that shark is deleted, we can include the dependent option with the association.

      Add the following code to the file to ensure that the destroy action on a given shark deletes any associated posts:

      ~/sharkapp/app/models/post.rb

      class Shark < ApplicationRecord
        has_many :posts , dependent: :destroy
        validates :name, presence: true, uniqueness: true
        validates :facts, presence: true
      end
      

      Once you have finished making these changes, save and close the file. If you are using nano, you can do this by pressing CTRL+X, Y, then ENTER.

      Next, open your config/routes.rb file to modify the relationship between your resourceful routes:

      Currently, the file looks like this:

      ~/sharkapp/config/routes.rb

      Rails.application.routes.draw do
        resources :posts 
        resources :sharks
      
        root 'sharks#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      The current code establishes an independent relationship between our routes, when what we would like to express is a dependent relationship between sharks and their associated posts.

      Let’s update our route declaration to make :sharks the parent of :posts. Update the code in the file to look like the following:

      ~/sharkapp/config/routes.rb

      Rails.application.routes.draw do
        resources :sharks do
          resources :posts
        end
        root 'sharks#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      Save and close the file when you are finished editing.

      With these changes in place, you can move on to updating your posts controller.

      Step 3 — Updating the Posts Controller

      The association between our models gives us methods that we can use to create new post instances associated with particular sharks. To use these methods, we will need to add them our posts controller.

      Open the posts controller file:

      • nano app/controllers/posts_controller.rb

      Currently, the file looks like this:

      ~/sharkapp/controllers/posts_controller.rb

      class PostsController < ApplicationController
        before_action :set_post, only: [:show, :edit, :update, :destroy]
      
        # GET /posts
        # GET /posts.json
        def index
          @posts = Post.all
        end
      
        # GET /posts/1
        # GET /posts/1.json
        def show
        end
      
        # GET /posts/new
        def new
          @post = Post.new
        end
      
        # GET /posts/1/edit
        def edit
        end
      
        # POST /posts
        # POST /posts.json
        def create
          @post = Post.new(post_params)
      
          respond_to do |format|
            if @post.save
              format.html { redirect_to @post, notice: 'Post was successfully created.' }
              format.json { render :show, status: :created, location: @post }
            else
              format.html { render :new }
              format.json { render json: @post.errors, status: :unprocessable_entity }
            end
          end
        end
      
        # PATCH/PUT /posts/1
        # PATCH/PUT /posts/1.json
        def update
          respond_to do |format|
            if @post.update(post_params)
              format.html { redirect_to @post, notice: 'Post was successfully updated.' }
              format.json { render :show, status: :ok, location: @post }
            else
              format.html { render :edit }
              format.json { render json: @post.errors, status: :unprocessable_entity }
            end
          end
        end
      
        # DELETE /posts/1
        # DELETE /posts/1.json
        def destroy
          @post.destroy
          respond_to do |format|
            format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
            format.json { head :no_content }
          end
        end
      
        private
          # Use callbacks to share common setup or constraints between actions.
          def set_post
            @post = Post.find(params[:id])
          end
      
          # Never trust parameters from the scary internet, only allow the white list through.
          def post_params
            params.require(:post).permit(:body, :shark_id)
          end
      end
      

      Like our sharks controller, this controller’s methods work with instances of the associated Post class. For example, the new method creates a new instance of the Post class, the index method grabs all instances of the class, and the set_post method uses find and params to select a particular post by id. If, however, we want our post instances to be associated with particular shark instances, then we will need to modify this code, since the Post class is currently operating as an independent entity.

      Our modifications will make use of two things:

      • The methods that became available to us when we added the belongs_to and has_many associations to our models. Specifically, we now have access to the build method thanks to the has_many association we defined in our Shark model. This method will allow us to create a collection of post objects associated with a particular shark object, using the shark_id foreign key that exists in our posts database.
      • The routes and routing helpers that became available when we created a nested posts route. For a full list of example routes that become available when you create nested relationships between resources, see the Rails documentation. For now, it will be enough for us to know that for each specific shark — say sharks/1 — there will be an associated route for posts related to that shark: sharks/1/posts. There will also be routing helpers like shark_posts_path(@shark) and edit_sharks_posts_path(@shark) that refer to these nested routes.

      In the file, we’ll begin by writing a method, get_shark, that will run before each action in the controller. This method will create a local @shark instance variable by finding a shark instance by shark_id. With this variable available to us in the file, it will be possible to relate posts to a specific shark in the other methods.

      Above the other private methods at the bottom of the file, add the following method:

      ~/sharkapp/controllers/posts_controller.rb

      . . . 
      private
        def get_shark
          @shark = Shark.find(params[:shark_id])
        end
        # Use callbacks to share common setup or constraints between actions.
      . . . 
      

      Next, add the corresponding filter to the top of the file, before the existing filter:

      ~/sharkapp/controllers/posts_controller.rb

      class PostsController < ApplicationController
        before_action :get_shark
      

      This will ensure that get_shark runs before each action defined in the file.

      Next, you can use this @shark instance to rewrite the index method. Instead of grabbing all instances of the Post class, we want this method to return all post instances associated with a particular shark instance.

      Modify the index method to look like this:

      ~/sharkapp/controllers/posts_controller.rb

      . . .
        def index
          @posts = @shark.posts
        end
      . . .
      

      The new method will need a similar revision, since we want a new post instance to be associated with a particular shark. To achieve this, we can make use of the build method, along with our local @shark instance variable.

      Change the new method to look like this:

      ~/sharkapp/controllers/posts_controller.rb

      . . . 
        def new
          @post = @shark.posts.build
        end
      . . . 
      

      This method creates a post object that’s associated with the specific shark instance from the get_shark method.

      Next, we’ll address the method that’s most closely tied to new: create. The create method does two things: it builds a new post instance using the parameters that users have entered into the new form, and, if there are no errors, it saves that instance and uses a route helper to redirect users to where they can see the new post. In the case of errors, it renders the new template again.

      Update the create method to look like this:

      ~/sharkapp/controllers/posts_controller.rb

        def create
          @post = @shark.posts.build(post_params)
      
              respond_to do |format|
               if @post.save  
                  format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully created.' }
                  format.json { render :show, status: :created, location: @post }
               else
                  format.html { render :new }
                  format.json { render json: @post.errors, status: :unprocessable_entity }
            end
          end
        end
      

      Next, take a look at the update method. This method uses a @post instance variable, which is not explicitly set in the method itself. Where does this variable come from?

      Take a look at the filters at the top of the file. The second, auto-generated before_action filter provides an answer:

      ~/sharkapp/controllers/posts_controller.rb

      class PostsController < ApplicationController
        before_action :get_shark
        before_action :set_post, only: [:show, :edit, :update, :destroy]
        . . .
      

      The update method (like show, edit, and destroy) takes a @post variable from the set_post method. That method, listed under the get_shark method with our other private methods, currently looks like this:

      ~/sharkapp/controllers/posts_controller.rb

      . . . 
      private
      . . . 
        def set_post
          @post = Post.find(params[:id])
        end
      . . .
      

      In keeping with the methods we’ve used elsewhere in the file, we will need to modify this method so that @post refers to a particular instance in the collection of posts that’s associated with a particular shark. Keep the build method in mind here — thanks to the associations between our models, and the methods (like build) that are available to us by virtue of those associations, each of our post instances is part of a collection of objects that’s associated with a particular shark. So it makes sense that when querying for a particular post, we would query the collection of posts associated with a particular shark.

      Update set_post to look like this:

      ~/sharkapp/controllers/posts_controller.rb

      . . . 
      private
      . . . 
        def set_post
          @post = @shark.posts.find(params[:id])
        end
      . . .
      

      Instead of finding a particular instance of the entire Post class by id, we instead search for a matching id in the collection of posts associated with a particular shark.

      With that method updated, we can look at the update and destroy methods.

      The update method makes use of the @post instance variable from set_post, and uses it with the post_params that the user has entered in the edit form. In the case of success, we want Rails to send the user back to the index view of the posts associated with a particular shark. In the case of errors, Rails will render the edit template again.

      In this case, the only change we will need to make is to the redirect_to statement, to handle successful updates. Update it to redirect to shark_post_path(@shark), which will redirect to the index view of the selected shark’s posts:

      ~/sharkapp/controllers/posts_controller.rb

      . . . 
        def update
          respond_to do |format|
            if @post.update(post_params)
              format.html { redirect_to shark_post_path(@shark), notice: 'Post was successfully updated.' }
              format.json { render :show, status: :ok, location: @post }
            else
              format.html { render :edit }
              format.json { render json: @post.errors, status: :unprocessable_entity }
            end
          end
        end
      . . .
      

      Next, we will make a similar change to the destroy method. Update the redirect_to method to redirect requests to shark_posts_path(@shark) in the case of success:

      ~/sharkapp/controllers/posts_controller.rb

      . . . 
        def destroy
          @post.destroy
           respond_to do |format|
            format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully destroyed.' }
            format.json { head :no_content }
          end
        end
      . . .
      

      This is the last change we will make. You now have a posts controller file that looks like this:

      ~/sharkapp/controllers/posts_controller.rb

      class PostsController < ApplicationController
        before_action :get_shark
        before_action :set_post, only: [:show, :edit, :update, :destroy]
      
        # GET /posts
        # GET /posts.json
        def index
          @posts = @shark.posts
        end
      
        # GET /posts/1
        # GET /posts/1.json
        def show
        end
      
        # GET /posts/new
        def new
          @post = @shark.posts.build
        end
      
        # GET /posts/1/edit
        def edit
        end
      
        # POST /posts
        # POST /posts.json
        def create
          @post = @shark.posts.build(post_params)
      
              respond_to do |format|
               if @post.save  
                  format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully created.' }
                  format.json { render :show, status: :created, location: @post }
               else
                  format.html { render :new }
                  format.json { render json: @post.errors, status: :unprocessable_entity }
            end
          end
        end
      
        # PATCH/PUT /posts/1
        # PATCH/PUT /posts/1.json
        def update
          respond_to do |format|
            if @post.update(post_params)
              format.html { redirect_to shark_post_path(@shark), notice: 'Post was successfully updated.' }
              format.json { render :show, status: :ok, location: @post }
            else
              format.html { render :edit }
              format.json { render json: @post.errors, status: :unprocessable_entity }
            end
          end
        end
      
        # DELETE /posts/1
        # DELETE /posts/1.json
        def destroy
          @post.destroy
          respond_to do |format|
            format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully destroyed.' }
            format.json { head :no_content }
          end
        end
      
        private
      
         def get_shark
           @shark = Shark.find(params[:shark_id])
         end
          # Use callbacks to share common setup or constraints between actions.
          def set_post
            @post = @shark.posts.find(params[:id])
          end
      
          # Never trust parameters from the scary internet, only allow the white list through.
          def post_params
            params.require(:post).permit(:body, :shark_id)
          end
      end
      

      The controller manages how information is passed from the view templates to the database and vice versa. Our controller now reflects the relationship between our Shark and Post models, in which posts are associated with particular sharks. We can move on to modifying the view templates themselves, which are where users will pass in and modify post information about particular sharks.

      Step 4 — Modifying Views

      Our view template revisions will involve changing the templates that relate to posts, and also modifying our sharks show view, since we want users to see the posts associated with particular sharks.

      Let’s start with the foundational template for our posts: the form partial that is reused across multiple post templates. Open that form now:

      • nano app/views/posts/_form.html.erb

      Rather than passing only the post model to the form_with form helper, we will pass both the shark and post models, with post set as a child resource.

      Change the first line of the file to look like this, reflecting the relationship between our shark and post resources:

      ~/sharkapp/views/posts/_form.html.erb

      <%= form_with(model: [@shark, post], local: true) do |form| %>
      . . . 
      

      Next, delete the section that lists the shark_id of the related shark, since this is not essential information in the view.

      The finished form, complete with our edits to the first line and without the deleted shark_id section, will look like this:

      ~/sharkapp/views/posts/_form.html.erb

      <%= form_with(model: [@shark, post], local: true) do |form| %>
        <% if post.errors.any? %>
          <div id="error_explanation">
            <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>
      
            <ul>
            <% post.errors.full_messages.each do |message| %>
              <li><%= message %></li>
            <% end %>
            </ul>
          </div>
        <% end %>
      
        <div class="field">
          <%= form.label :body %>
          <%= form.text_area :body %>
        </div>
      
        <div class="actions">
          <%= form.submit %>
        </div>
      <% end %>
      

      Save and close the file when you are finished editing.

      Next, open the index view, which will show the posts associated with a particular shark:

      • nano app/views/posts/index.html.erb

      Thanks to the rails generate scaffold command, Rails has generated the better part of the template, complete with a table that shows the body field of each post and its associated shark.

      Much like the other code we have already modified, however, this template treats posts as independent entities, when we would like to make use of the associations between our models and the collections and helper methods that these associations give us.

      In the body of the table, make the following updates:

      First, update post.shark to post.shark.name, so that the table will include the name field of the associated shark, rather than identifying information about the shark object itself:

      ~/sharkapp/app/views/posts/index.html.erb

      . . . 
        <tbody>
          <% @posts.each do |post| %>
            <tr>
              <td><%= post.body %></td>
              <td><%= post.shark.name %></td>
      . . . 
      

      Next, change the Show redirect to direct users to the show view for the associated shark, since they will most likely want a way to navigate back to the original shark. We can make use of the @shark instance variable that we set in the controller here, since Rails makes instance variables created in the controller available to all views. We’ll also change the text for the link from Show to Show Shark, so that users will better understand its function.

      Update the this line to the following:

      ~/sharkapp/app/views/posts/index.html.erb

      . . . 
        <tbody>
          <% @posts.each do |post| %>
            <tr>
              <td><%= post.body %></td>
              <td><%= post.shark.name %></td>
              <td><%= link_to 'Show Shark', [@shark] %></td>
      

      In the next line, we want to ensure that users are routed the right nested path when they go to edit a post. This means that rather than being directed to posts/post_id/edit, users will be directed to sharks/shark_id/posts/post_id/edit. To do this, we’ll use the shark_post_path routing helper and our models, which Rails will treat as URLs. We’ll also update the link text to make its function clearer.

      Update the Edit line to look like the following:

      ~/sharkapp/app/views/posts/index.html.erb

      . . . 
        <tbody>
          <% @posts.each do |post| %>
            <tr>
              <td><%= post.body %></td>
              <td><%= post.shark.name %></td>
              <td><%= link_to 'Show Shark', [@shark] %></td>
              <td><%= link_to 'Edit Post', edit_shark_post_path(@shark, post) %></td>
      

      Next, let’s add a similar change to the Destroy link, updating its function in the string, and adding our shark and post resources:

      ~/sharkapp/app/views/posts/index.html.erb

      . . . 
        <tbody>
          <% @posts.each do |post| %>
            <tr>
              <td><%= post.body %></td>
              <td><%= post.shark.name %></td>
              <td><%= link_to 'Show Shark', [@shark] %></td>
              <td><%= link_to 'Edit Post', edit_shark_post_path(@shark, post) %></td>
              <td><%= link_to 'Destroy Post', [@shark, post], method: :delete, data: { confirm: 'Are you sure?' } %></td>
      

      Finally, at the bottom of the form, we will want to update the New Post path to take users to the appropriate nested path when they want to create a new post. Update the last line of the file to make use of the new_shark_post_path(@shark) routing helper:

      ~/sharkapp/app/views/posts/index.html.erb

      . . . 
      <%= link_to 'New Post', new_shark_post_path(@shark) %>
      

      The finished file will look like this:

      ~/sharkapp/app/views/posts/index.html.erb

      <p id="notice"><%= notice %></p>
      
      <h1>Posts</h1>
      
      <table>
        <thead>
          <tr>
            <th>Body</th>
            <th>Shark</th>
            <th colspan="3"></th>
          </tr>
        </thead>
      
        <tbody>
          <% @posts.each do |post| %>
            <tr>
              <td><%= post.body %></td>
              <td><%= post.shark.name %></td>
              <td><%= link_to 'Show Shark', [@shark] %></td>
              <td><%= link_to 'Edit Post', edit_shark_post_path(@shark, post) %></td>
              <td><%= link_to 'Destroy Post', [@shark, post], method: :delete, data: { confirm: 'Are you sure?' } %></td>
            </tr>
          <% end %>
        </tbody>
      </table>
      
      <br>
      
      <%= link_to 'New Post', new_shark_post_path(@shark) %>
      

      Save and close the file when you are finished editing.

      The other edits we will make to post views won’t be as numerous, since our other views use the form partial we have already edited. However, we will want to update the link_to references in the other post templates to reflect the changes we have made to our form partial.

      Open app/views/posts/new.html.erb:

      • nano app/views/posts/new.html.erb

      Update the link_to reference at the bottom of the file to make use of the shark_posts_path(@shark) helper:

      ~/sharkapp/app/views/posts/new.html.erb

      . . . 
      <%= link_to 'Back', shark_posts_path(@shark) %>
      

      Save and close the file when you are finished making this change.

      Next, open the edit template:

      • nano app/views/posts/edit.html.erb

      In addition to the Back path, we’ll update Show to reflect our nested resources. Change the last two lines of the file to look like this:

      ~/sharkapp/app/views/posts/edit.html.erb

      . . . 
      <%= link_to 'Show', [@shark, @post] %> |
      <%= link_to 'Back', shark_posts_path(@shark) %>
      

      Save and close the file.

      Next, open the show template:

      • nano app/views/posts/show.html.erb

      Make the following edits to the Edit and Back paths at the bottom of the file:

      ~/sharkapp/app/views/posts/edit.html.erb

      . . .
      <%= link_to 'Edit', edit_shark_post_path(@shark, @post) %> |
      <%= link_to 'Back', shark_posts_path(@shark) %>
      

      Save and close the file when you are finished.

      As a final step, we will want to update the show view for our sharks so that posts are visible for individual sharks. Open that file now:

      • nano app/views/sharks/show.html.erb

      Our edits here will include adding a Posts section to the form and an Add Post link at the bottom of the file.

      Below the Facts for a given shark, we will add a new section that iterates through each instance in the collection of posts associated with this shark, outputting the body of each post.

      Add the following code below the Facts section of the form, and above the redirects at the bottom of the file:

      ~/sharkapp/app/views/sharks/show.html.erb

      . . .
      <p>
        <strong>Facts:</strong>
        <%= @shark.facts %>
      </p>
      
      <h2>Posts</h2>
      <% for post in @shark.posts %>
          <ul>
            <li><%= post.body %></li>
        </ul>
      <% end %>
      
      <%= link_to 'Edit', edit_shark_path(@shark) %> |
      . . . 
      

      Next, add a new redirect to allow users to add a new post for this particular shark:

      ~/sharkapp/app/views/sharks/show.html.erb

      . . .
      <%= link_to 'Edit', edit_shark_path(@shark) %> |
      <%= link_to 'Add Post', shark_posts_path(@shark) %> |
      <%= link_to 'Back', sharks_path %>
      

      Save and close the file when you are finished editing.

      You have now made changes to your application’s models, controllers, and views to ensure that posts are always associated with a particular shark. As a final step, we can add some validations to our Post model to guarantee consistency in the data that’s saved to the database.

      Step 5 — Adding Validations and Testing the Application

      In Step 5 of How To Build a Ruby on Rails Application, you added validations to your Shark model to ensure uniformity and consistency in the data that gets saved to the sharks database. We’ll now take a similar step to ensure guarantees for the posts database as well.

      Open the file where your Post model is defined:

      Here, we want to ensure that posts are not blank and that they don’t duplicate content other users may have posted. To achieve this, add the following line to the file:

      ~/sharkapp/app/models/post.rb

      class Post < ApplicationRecord
        belongs_to :shark
        validates :body, presence: true, uniqueness: true
      end
      

      Save and close the file when you are finished editing.

      With this last change in place, you are ready to run your migrations and test the application.

      First, run your migrations:

      Next, start your server. If you’re working locally, you can do so by running:

      If you are working on a development server, run the following command instead:

      • rails s --binding=your_server_ip

      Navigate to your application’s root at http://localhost:3000 or http://your_server_ip:3000.

      The prerequisite Rails project tutorial walked you through adding and editing a Great White shark entry. If you have not added any further sharks, the application landing page will look like this:

      Shark App Landing Page

      Click on Show next to the Great White’s name. This will take you to the show view for this shark. You will see the name of the shark and its facts, and a Posts header with no content. Let’s add a post to populate this part of the form.

      Click on Add Post below the Posts header. This will bring you to the post index view, where you will have the chance to select New Post:

      Post Index View

      Thanks to the authentication mechanisms you put in place in Step 6 of How To Build a Ruby on Rails Application, you may be asked to authenticate with the username and password you created in that Step, depending on whether or not you have created a new session.

      Click on New Post, which will bring you to your post new template:

      New Post

      In the Body field, type, “These sharks are scary!”

      New Shark Post

      Click on Create Post. You will be redirected to the index view for all posts that belong to this shark:

      Post Success

      With our post resources working, we can now test our data validations to ensure that only desired data gets saved to the database.

      From the index view, click on New Post. In the Body field of the new form, try entering “These sharks are scary!” again:

      Repeat Shark Post

      Click on Create Post. You will see the following error:

      Unique Post Error

      Click on Back to return to the main posts page.

      To test our other validation, click on New Post again. Leave the post blank and click Create Post. You will see the following error:

      Blank Post Error

      With your nested resources and validations working properly, you now have a working Rails application that you can use as a starting point for further development.

      Conclusion

      With your Rails application in place, you can now work on things like styling and developing other front-end components. If you would like to learn more about routing and nested resources, the Rails documentation is a great place to start.

      To learn more about integrating front-end frameworks with your application, take a look at How To Set Up a Ruby on Rails Project with a React Frontend.



      Source link

      How To Build a Ruby on Rails Application


      Introduction

      Rails is a web application framework written in Ruby. It takes an opinionated approach to application development, assuming that set conventions best serve developers where there is a common goal. Rails therefore offers conventions for handling routing, stateful data, asset management, and more to provide the baseline functionality that most web applications need.

      Rails follows the model-view-controller (MCV) architectural pattern, which separates an application’s logic, located in models, from the routing and presentation of application information. This organizational structure — along with other conventions that allow developers to extract code into helpers and partials — ensures that application code isn’t repeated unnecessarily.

      In this tutorial, you will build a Rails application that will enable users to post information about sharks and their behavior. It will be a good starting point for future application development.

      Prerequisites

      To follow this tutorial, you will need:

      • A local machine or development server running Ubuntu 18.04. Your development machine should have a non-root user with administrative privileges and a firewall configured with ufw. For instructions on how to set this up, see our Initial Server Setup with Ubuntu 18.04 tutorial.
      • Node.js and npm installed on your local machine or development server. This tutorial uses Node.js version 10.16.3 and npm version 6.9.0. For guidance on installing Node.js and npm on Ubuntu 18.04, follow the instructions in the “Installing Using a PPA” section of How To Install Node.js on Ubuntu 18.04.
      • Ruby, rbenv, and Rails installed on your local machine or development server, following Steps 1-4 in How To Install Ruby on Rails with rbenv on Ubuntu 18.04. This tutorial uses Ruby 2.5.1, rbenv 1.1.2, and Rails 5.2.0.

      Step 1 — Installing SQLite3

      Before creating our Rails shark application, we will need to ensure that we have a database to store user data. Rails is configured to use SQLite by default, and this is often a good choice in development. Since our application data doesn’t require a high level programmatic extensibility, SQLite will meet our needs.

      First, update your package index:

      Next, install the sqlite3 and libsqlite3-dev packages:

      • sudo apt install sqlite3 libsqlite3-dev

      This will install both SQLite and its required development files.

      Check your version to confirm that the installation was successful:

      Output

      3.22.0 2018-01-22 18:45:57 0c55d179733b46d8d0ba4d88e01a25e10677046ee3da1d5b1581e86726f2alt1

      With SQLite installed, you are ready to begin developing your application.

      Step 2 — Creating a New Rails Project

      With our database installed, we can create a new Rails project and look at some of the default boilerplate code that Rails gives us with the rails new command.

      Create a project called sharkapp with the following command:

      You will see a good deal of output telling you what Rails is creating for your new project. The output below highlights some significant files, directories, and commands:

      Output

      create . . . create Gemfile . . . create app . . . create app/controllers/application_controller.rb . . . create app/models/application_record.rb . . . create app/views/layouts/application.html.erb . . . create config create config/routes.rb create config/application.rb . . . create config/environments create config/environments/development.rb create config/environments/production.rb create config/environments/test.rb . . . create config/database.yml create db create db/seeds.rb . . . run bundle install . . . Bundle complete! 18 Gemfile dependencies, 78 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. . . . * bin/rake: Spring inserted * bin/rails: Spring inserted

      The output highlighted here tells you that Rails has created the following:

      • Gemfile: This file lists the gem dependencies for your application. A gem is a Ruby software package, and a Gemfile allows you to manage your project's software needs.
      • app: The app directory is where your main application code lives. This includes the models, controllers, views, assets, helpers, and mailers that make up the application itself. Rails gives you some application-level boilerplate for the MCV model to start out in files like app/models/application_record.rb, app/controllers/application_controller.rb, and app/views/layouts/application.html.erb.
      • config: This directory contains your application's configuration settings:
        • config/routes.rb: Your application's route declarations live in this file.
        • config/application.rb: General settings for your application components are located in this file.
      • config/environments: This directory is where configuration settings for your environments live. Rails includes three environments by default: development, production, and test.
      • config/database.yml: Database configuration settings live in this file, which is broken into four sections: default, development, production, and test. Thanks to the Gemfile that came with the rails new command, which included the sqlite3 gem, our config/database.yml file has its adapter parameter set to sqlite3 already, specifying that we will use an SQLite database with this application.
      • db: This folder includes a directory for database migrations called migrate, along with the schema.rb and seeds.rb files. schema.db contains information about your database, while seeds.rb is where you can place seed data for the database.

      Finally, Rails runs the bundle install command to install the dependencies listed in your Gemfile.

      Once everything is set up, navigate to the sharkapp directory:

      You can now start the Rails server to ensure that your application is working, using the rails server command. If you are working on your local machine, type:

      Rails binds to localhost by default, so you can now access your application by navigating your browser to locahost:3000, where you will see the following image:

      Rails Landing Page

      If you are working on a development server, first ensure that connections are allowed on port 3000:

      Then start the server with the --binding flag, to bind to your server IP:

      • rails server --binding=your_server_ip

      Navigate to http://your_server_ip:3000 in your browser, where you will see the Rails welcome message.

      Once you have looked around, you can stop the server with CTRL+C.

      With your application created and in place, you are ready to start building from the Rails boilerplate to create a unique application.

      Step 3 — Scaffolding the Application

      To create our shark information application, we will need to create a model to manage our application data, views to enable user interaction with that data, and a controller to manage communication between the model and the views. To build these things we will use the rails generate scaffold command, which will give us a model, a database migration to alter the database schema, a controller, a full set of views to manage Create, Read, Update, and Delete (CRUD) operations for the application, and templates for partials, helpers, and tests.

      Because the generate scaffold command does so much work for us, we'll take a closer look at the resources it creates to understand the work that Rails is doing under the hood.

      Our generate scaffold command will include the name of our model and the fields we want in our database table. Rails uses Active Record to manage relationships between application data, constructed as objects with models, and the application database. Each of our models is a Ruby class, while also inheriting from the ActiveRecord::Base class. This means that we can work with our model class in the same way that we would work with a Ruby class, while also pulling in methods from Active Record. Active Record will then ensure that each class is mapped to a table in our database, and each instance of that class to a row in that table.

      Type the following command to generate a Shark model, controller, and associated views:

      • rails generate scaffold Shark name:string facts:text

      With name:string and facts:text we are giving Rails information about the fields we would like in our database table and the type of data they should accept. Both will give us room to input what we would like, though text will allow more characters for shark facts.

      When you type this command, you will again see a long list of output that explains everything Rails is generating for you. The output below highlights some of the more significant things for our setup:

      Output

      invoke active_record create db/migrate/20190804181822_create_sharks.rb create app/models/shark.rb . . . invoke resource_route route resources :sharks invoke scaffold_controller create app/controllers/sharks_controller.rb invoke erb create app/views/sharks create app/views/sharks/index.html.erb create app/views/sharks/edit.html.erb create app/views/sharks/show.html.erb create app/views/sharks/new.html.erb create app/views/sharks/_form.html.erb . . .

      Rails has created the model at app/models/shark.rb and a database migration to go with it: db/migrate/20190804181822_create_sharks.rb. The timestamp on your migration file will differ from what you see here.

      It has also created a controller, app/controllers/sharks_controller.rb, as well as the views associated with our application's CRUD operations, collected under app/views/sharks. Among these views is a partial, _form.html.erb, that contains code used across views.

      Finally, Rails added a new resourceful route, resources :sharks, to config/routes.rb. This enables the Rails router to match incoming HTTP requests with the sharks controller and its associated views.

      Though Rails has done much of the work of building out our application code for us, it is worth taking a look at some files to understand what is happening.

      First, let's look at the controller file with the following command:

      • cat app/controllers/sharks_controller.rb

      Output

      class SharksController < ApplicationController before_action :set_shark, only: [:show, :edit, :update, :destroy] # GET /sharks # GET /sharks.json def index @sharks = Shark.all end # GET /sharks/1 # GET /sharks/1.json def show end # GET /sharks/new def new @shark = Shark.new end # GET /sharks/1/edit def edit end # POST /sharks # POST /sharks.json def create @shark = Shark.new(shark_params) respond_to do |format| if @shark.save format.html { redirect_to @shark, notice: 'Shark was successfully created.' } format.json { render :show, status: :created, location: @shark } else format.html { render :new } format.json { render json: @shark.errors, status: :unprocessable_entity } end end end # PATCH/PUT /sharks/1 # PATCH/PUT /sharks/1.json def update respond_to do |format| if @shark.update(shark_params) format.html { redirect_to @shark, notice: 'Shark was successfully updated.' } format.json { render :show, status: :ok, location: @shark } else format.html { render :edit } format.json { render json: @shark.errors, status: :unprocessable_entity } end end end # DELETE /sharks/1 # DELETE /sharks/1.json def destroy @shark.destroy respond_to do |format| format.html { redirect_to sharks_url, notice: 'Shark was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_shark @shark = Shark.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def shark_params params.require(:shark).permit(:name, :facts) end end

      The controller is responsible for managing how information gets fetched and passed to its associated model, and how it gets associated with particular views. As you can see, our sharks controller includes a series of methods that map roughly to standard CRUD operations. However, there are more methods than CRUD functions, to enable efficiency in the case of errors.

      For example, consider the create method:

      ~/sharkapp/app/controllers/sharks_controller.rb

      . . .
        def create
          @shark = Shark.new(shark_params)
      
          respond_to do |format|
            if @shark.save
              format.html { redirect_to @shark, notice: 'Shark was successfully created.' }
              format.json { render :show, status: :created, location: @shark }
            else
              format.html { render :new }
              format.json { render json: @shark.errors, status: :unprocessable_entity }
            end
          end
        end
      . . . 
      

      If a new instance of the Shark class is successfully saved, redirect_to will spawn a new request that is then directed to the controller. This will be a GET request, and it will be handled by the show method, which will show the user the shark they've just added.

      If there is a failure, then Rails will render the app/views/sharks/new.html.erb template again rather than making another request to the router, giving users another chance to submit their data.

      In addition to the sharks controller, Rails has given us a template for an index view, which maps to the index method in our controller. We will use this as the root view for our application, so it's worth taking a look at it.

      Type the following to output the file:

      • cat app/views/sharks/index.html.erb

      Output

      <p id="notice"><%= notice %></p> <h1>Sharks</h1> <table> <thead> <tr> <th>Name</th> <th>Facts</th> <th colspan="3"></th> </tr> </thead> <tbody> <% @sharks.each do |shark| %> <tr> <td><%= shark.name %></td> <td><%= shark.facts %></td> <td><%= link_to 'Show', shark %></td> <td><%= link_to 'Edit', edit_shark_path(shark) %></td> <td><%= link_to 'Destroy', shark, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> </table> <br> <%= link_to 'New Shark', new_shark_path %>

      The index view iterates through the instances of our Shark class, which have been mapped to the sharks table in our database. Using ERB templating, the view outputs each field from the table that is associated with an individual shark instance: name and facts.

      The view then uses the link_to helper to create a hyperlink, with the provided string as the text for the link and the provided path as the destination. The paths themselves are made possible through the helpers that became available to us when we defined the sharks resourceful route with the rails generate scaffold command.

      In addition to looking at our index view, we can also take a look at the new view to see how Rails uses partials in views. Type the following to output the app/views/sharks/new.html.erb template:

      • cat app/views/sharks/new.html.erb

      Output

      <h1>New Shark</h1> <%= render 'form', shark: @shark %> <%= link_to 'Back', sharks_path %>

      Though this template may look like it lacks input fields for a new shark entry, the reference to render 'form' tells us that the template is pulling in the _form.html.erb partial, which extracts code that is repeated across views.

      Looking at that file will give us a full sense of how a new shark instance gets created:

      • cat app/views/sharks/_form.html.erb

      Output

      <%= form_with(model: shark, local: true) do |form| %> <% if shark.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(shark.errors.count, "error") %> prohibited this shark from being saved:</h2> <ul> <% shark.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= form.label :name %> <%= form.text_field :name %> </div> <div class="field"> <%= form.label :facts %> <%= form.text_area :facts %> </div> <div class="actions"> <%= form.submit %> </div> <% end %>

      This template makes use of the form_with form helper. Form helpers are designed to facilitate the creation of new objects from user input using the fields and scope of particular models. Here, form_with takes model: shark as an argument, and the new form builder object that it creates has field inputs that correspond to the fields in the sharks table. Thus users have form fields to enter both a shark name and shark facts.

      Submitting this form will create a JSON response with user data that the rest of your application can access by way of the params method, which creates a ActionController::Parameters object with that data.

      Now that you know what rails generate scaffold has produced for you, you can move on to setting the root view for your application.

      Step 4 — Creating the Application Root View and Testing Functionality

      Ideally, you want the landing page of your application to map to the application's root, so users can immediately get a sense of the application's purpose.

      There are a number of ways you could handle this: for example, you could create a Welcome controller and an associated index view, which would give users a generic landing page that could also link out to different parts of the application. In our case, however, having users land on our index sharks view will be enough of an introduction to the application's purpose for now.

      To set this up, you will need to modify the routing settings in config/routes.rb to specify the root of the application.

      Open config/routes.rb for editing, using nano or your favorite editor:

      The file will look like this:

      ~/sharkapp/config/routes.rb

      Rails.application.routes.draw do
        resources :sharks
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      Without setting something more specific, the default view at http://localhost:3000 or http://your_server_ip:3000 will be the default Rails welcome page.

      In order to map the root view of the application to the index view of the sharks controller, you will need to add the following line to the file:

      ~/sharkapp/config/routes.rb

      Rails.application.routes.draw do
        resources :sharks
      
        root 'sharks#index' 
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      Now, when users navigate to your application root, they will see a full listing of sharks, and have the opportunity to create a new shark entry, look at existing entries, and edit or delete given entries.

      Save the file and exit your editor when you are finished editing. If you used nano to edit the file, you can do so by pressing CTRL+X, Y, then ENTER

      You can now run your migrations with the following command:

      You will see output confirming the migration.

      Start your Rails server once again. If you are working locally, type:

      On a development server, type:

      • rails s --binding=your_server_ip

      Navigate to localhost:3000 if you are working locally, or http://your_server_ip:3000 if you are working on a development server.

      Your application landing page will look like this:

      Application Landing Page

      To create a new shark, click on the New Shark link at the bottom of the page, which will take you to the sharks/new route:

      Create New Shark

      Let's add some demo information to test our application. Input "Great White" into the Name field and "Scary" into the Facts field:

      Add Great White Shark

      Click on the Create button to create the shark.

      This will direct you to the show route, which, thanks to the before_action filter, is set with the set_shark method, which grabs the id of the shark we've just created:

      ~/sharkapp/app/controllers/sharks_controller.rb

      class SharksController < ApplicationController
        before_action :set_shark, only: [:show, :edit, :update, :destroy]
      
        . . . 
      
        def show
        end
      
        . . . 
      
        private
          # Use callbacks to share common setup or constraints between actions.
          def set_shark
            @shark = Shark.find(params[:id])
          end
        . . .
      

      Show Shark

      You can test the edit function now by clicking Edit on your shark entry. This will take you to the edit route for that shark:

      Edit Shark

      Change the facts about the Great White to read "Large" instead of "Scary" and click Update Shark. This will take you back to the show route:

      Updated Shark

      Finally, clicking Back will take you to your updated index view:

      New Index View

      Now that you have tested your application's basic functionality, you can add some validations and security checks to make everything more secure.

      Step 5 — Adding Validations

      Your shark application can accept input from users, but imagine a case where a user attempts to create a shark without adding facts to it, or creates an entry for a shark that's already in the database. You can create mechanisms to check data before it gets entered into the database by adding validations to your models. Since your application's logic is located in its models, validating data input here makes more sense than doing so elsewhere in the application.

      Note that we will not cover writing validation tests in this tutorial, but you can find out more about testing by consulting the Rails documentation.

      If you haven't stopped the server yet, go ahead and do that by typing CTRL+C.

      Open your shark.rb model file:

      Currently, the file tells us that the Shark class inherits from ApplicationRecord, which in turn inherits from ActiveRecord::Base:

      ~/sharkapp/app/models/shark.rb

      class Shark < ApplicationRecord
      end
      

      Let's first add some validations to our name field to confirm that the field is filled out and that the entry is unique, preventing duplicate entries:

      ~/sharkapp/app/models/shark.rb

      class Shark < ApplicationRecord
        validates :name, presence: true, uniqueness: true
      end
      

      Next, add a validation for the facts field to ensure that it, too, is filled out:

      ~/sharkapp/app/models/shark.rb

      class Shark < ApplicationRecord
        validates :name, presence: true, uniqueness: true
        validates :facts, presence: true
      end
      

      We are less concerned here with the uniqueness of the facts, as long as they are associated with unique shark entries.

      Save and close the file when you are finished.

      Start up your server once again with either rails s or rails s --binding=your_server_ip, depending on whether you are working locally or with a development server.

      Navigate to your application's root at http://localhost:3000 or http://your_server_ip:3000.

      Click on New Shark. In the form, add "Great White" to the Name field and "Big Teeth" to the Facts field, and then click on Create Shark. You should see the following warning:

      Unique Validation Warning

      Now, let's see if we can check our other validation. Click Back to return to the homepage, and then New Shark once again. In the new form, enter "Tiger Shark" in the Name field, and leave Facts blank. Clicking Create Shark will trigger the following warning:

      Fact Presence Warning

      With these changes, your application has some validations in place to ensure consistency in the data that's saved to the database. Now you can turn your attention to your application's users and defining who can modify application data.

      Step 6 — Adding Authentication

      With validations in place, we have some guarantees about the data that's being saved to the database. But what about users? If we don't want any and all users adding to the database, then we should add some authentication measures to ensure that only permitted users can add sharks. In order to do this, we'll use the http_basic_authenticate_with method, which will allow us to create a username and password combination to authenticate users.

      There are a number of ways to authenticate users with Rails, including working with the bcrypt or devise gems. For now, however, we will add a method to our application controller that will apply to actions across our application. This will be useful if we add more controllers to the application in the future.

      Stop your server again with CTRL+C.

      Open the file that defines your ApplicationController:

      • nano app/controllers/application_controller.rb

      Inside, you will see the definition for the ApplicationController class, which the other controllers in your application inherit from:

      ~/sharkapp/app/controllers/application_controller.rb

      class ApplicationController < ActionController::Base
      end
      

      To authenticate users, we'll use a hardcoded username and password with the http_basic_authenticate_with method. Add the following code to the file:

      ~/sharkapp/app/controllers/application_controller.rb

      class ApplicationController < ActionController::Base
        http_basic_authenticate_with name: 'sammy', password: 'shark', except: [:index, :show]
      end
      

      In addition to supplying the username and password here, we've also restricted authentication by specifying the routes where it should not be required: index and show. Another way of accomplishing this would have been to write only: [:create, :update, :destroy]. This way, all users will be able to look at all of the sharks and read facts about particular sharks. When it comes to modifying site content, however, users will need to prove that they have access.

      In a more robust setup, you would not want to hardcode values in this way, but for the purposes of demonstration, this will allow you to see how you can include authentication for your application's routes. It also lets you see how Rails stores session data by default in cookies: once you authenticate on a specified action, you will not be required to authenticate again in the same session.

      Save and close app/controllers/application_controller.rb when you are finished editing. You can now test authentication in action.

      Start the server with either rails s or rails s --binding=your_server_ip and navigate to your application at either http://localhost:3000 or http://your_server_ip:3000.

      On the landing page, click on the New Shark button. This will trigger the following authentication window:

      User Authentication

      If you enter the username and password combination you added to app/controllers/application_controller.rb, you will be able to securely create a new shark.

      You now have a working shark application, complete with data validations and a basic authentication scheme.

      Conclusion

      The Rails application you created in this tutorial is a jumping off point that you can use for further development. If you are interested in exploring the Rails ecosystem, the project documentation is a great place to start.

      Additionally, you might want to explore how to set up a more robust frontend for your project with a framework such as React. How To Set Up a Ruby on Rails Project with a React Frontend offers guidance on how to do this.

      If you would like to explore different database options, you can also check out How To Use PostgreSQL with Your Ruby on Rails Application on Ubuntu 18.04, which walks through how to work with PostgreSQL instead of SQLite. You can also consult our library of PostgreSQL tutorials to learn more about working with this database.



      Source link