sexta-feira, 17 de março de 2017

RAILS Creating a simple Ruby on RAILS application with PostgreSQL database

1. Introduction

This post gathers information about creating a Ruby on RAILS application with PostgreSQL

2. Step-by-step

2.1. Create a Ruby on RAILS with PostgreSQL database

$ pwd
/home/rails-home
$ rails new myrailsapppostgres -d postgresql


2.2.  Grant PostgreSQL database permission for administration user 'root' used on RAILS

$ psql
postgres=# CREATE USER root WITH PASSWORD 'root';
postgres=# GRANT postgres TO root;
postgres=# ALTER USER root CREATEDB;
postgres=# \q


2.3.  Create RAILS database

$ pwd
/home/rails-home/myrailsapppostgres
$ rake db:create
Created database 'myrailsapppostgres_development'
Created database 'myrailsapppostgres_test'


2.4.  Configure RAILS to listen for any 0.0.0.0 IP adddress on Port 3000 (default is 127.0.0.1:3000) 

$ pwd
/home/rails-home/myrailsapppostgres
$ vim ./config/boot.rb
    :
    require 'rails/commands/server'
    module Rails
      class Server
        def default_options
          super.merge(Host:  '0.0.0.0', Port: 3000)
        end
      end
    end
    :


2.5.  Create Welcome page

$ pwd
/home/rails-home/myrailsapppostgres
$ rails generate controller Welcome index
$ vim app/views/welcome/index.html.erb
<h1>Welcome#index</h1>
<p>Find me in app/views/welcome/index.html.erb</p>
$ vim config/routes.rb
Rails.application.routes.draw do
  get 'welcome/index'
  root 'welcome#index'
end


2.6. Exploring RAILS files and folders

  • app/: Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.
  • bin/: Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.
  • config/: Configure your application's routes, database, and more. This is covered in more detail in Configuring Rails Applications.
  • config.ru: Rack configuration for Rack based servers used to start the application.
  • db/: Contains your current database schema, as well as the database migrations.
  • Gemfile 
  • Gemfile.lock : These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the Bundler website.
  • lib/: Extended modules for your application.
  • log/: Application log files.
  • public/: The only folder seen by the world as-is. Contains static files and compiled assets.
  • Rakefile: This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.
  • README.md: This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.
  • test/: Unit tests, fixtures, and other test apparatus. These are covered in Testing Rails Applications.
  • tmp/: Temporary files (like cache and pid files).
  • vendor/: A place for all third-party code. In a typical Rails application this includes vendored gems.


2.7. Creating a simple RAILS application

a) create a resource 'articles' and check routes

$ pwd
/root/rails-home/myrailsapppostgres
$ vim config/routes.rb
Rails.application.routes.draw do
  get 'welcome/index'

  resources :articles
  root 'welcome#index'

end


b) check all RESTfull actions

$ rails routes
       Prefix Verb   URI Pattern                  Controller#Action
welcome_index GET    /welcome/index(.:format)     welcome#index
         root GET    /                            welcome#index
     articles GET    /articles(.:format)          articles#index
              POST   /articles(.:format)          articles#create
  new_article GET    /articles/new(.:format)      articles#new
 edit_article GET    /articles/:id/edit(.:format) articles#edit
      article GET    /articles/:id(.:format)      articles#show
              PATCH  /articles/:id(.:format)      articles#update
              PUT    /articles/:id(.:format)      articles#update
              DELETE /articles/:id(.:format)      articles#destroy


c) Create a model for article


  • Generate RAILS model for Article

$ rails generate model Article title:string text:text
Running via Spring preloader in process 3657
      invoke  active_record
      create    db/migrate/20170317185317_create_articles.rb
      create    app/models/article.rb
      invoke    test_unit
      create      test/models/article_test.rb
      create      test/fixtures/articles.yml



  • Checking migrate scripts automatically generated by RAILS

$ cat db/migrate/*_create_articles.rb 
class CreateArticles < ActiveRecord::Migration[5.0]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :text

      t.timestamps
    end
  end
end


cat app/models/article.rb
class Article < ApplicationRecord
  validates :title, presence: true, length: { minimum: 3 }
end


  • Run migrate scripts on environment (DEVELOPMENT)

$  rails db:migrate
== 20170317185317 CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.0092s
== 20170317185317 CreateArticles: migrated (0.0093s) ==========================



  • Checking on PostgreSQL databases databases created by scripts

$ psql -h 127.0.0.1 -d myrailsapppostgres_development -U root -W
myrailsapppostgres_development=> \l
               List of databases
             Name              |  Owner   
-------------------------------+----------
 myrailsapppostgres_development | root      ...
 myrailsapppostgres_test        | root      ...
          :                       :



  • Checking on PostgreSQL databases tables created by scripts

myrailsapppostgres_development=> \d
                List of relations
 Schema |         Name         |   Type   | Owner
--------+----------------------+----------+-------
 public | ar_internal_metadata | table    | root
 public | articles             | table    | root
 public | articles_id_seq      | sequence | root
 public | schema_migrations    | table    | root
(4 rows)



  • Checking on PostgreSQL databases table description created by scripts

myrailsapppostgres_development=> \d articles
                                     Table "public.articles"
   Column   |            Type             |                       Modifiers
------------+-----------------------------+-------------------------------------------------------
 id         | integer                     | not null default nextval('articles_id_seq'::regclass)
 title      | character varying           |
 text       | text                        |
 created_at | timestamp without time zone | not null
 updated_at | timestamp without time zone | not null
Indexes:
    "articles_pkey" PRIMARY KEY, btree (id)



d) Create controller for article and implement actions from CRUD capabilities

$ rails generate controller Articles
$ vim app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
end
$ vim app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def new       # new action
    @article = Article.new
  end

  def create    # create
    @article = Article.new(article_params)
    if @article.save
      redirect_to @article
    else
      render 'new'
    end
  end

  def show      # show
    @article = Article.find(params[:id])
  end

  def index     # list
    @articles = Article.all
  end

  def edit      # edit
    @article = Article.find(params[:id])
  end

  def update    # update
    @article = Article.find(params[:id])
    if @article.update(article_params)
      redirect_to @article
    else
      render 'edit'
    end
  end

  def destroy
    @article = Article.find(params[:id])
    @article.destroy
    redirect_to articles_path
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end

end



e) Create views for article and implement pages from CRUD capabilities: welcome index, new-create, show, list, edit and destroy


  • welcome#index: view for application Welcome 

$ vim app/views/welcome/index.html.erb
<h1>Welcome#index</h1>
<p>Find me in app/views/welcome/index.html.erb</p>
<%= link_to 'Articles', controller: 'articles' %>


  • new: view for new record

$ vim app/views/articles/new.html.erb
<h1>New Article</h1>

<%= form_for :article, url: articles_path do |f| %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>


  • show: view for show record

$ vim app/views/articles/show.html.erb
<h1>Show Article</h1>

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<%= link_to 'Back', articles_path %>


  • list: view for listing all records

$ vim app/views/articles/index.html.erb
<h1>Listing articles</h1>

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
  </tr>

  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
      <td><%= link_to 'Edit', edit_article_path(article) %></td>
      <td><%= link_to 'Destroy', article_path(article),
              method: :delete,
              data: { confirm: 'Are you sure?' } %></td>
    </tr>
  <% end %>
</table>


  • edit: view for edit record

$ vim app/views/articles/edit.html.erb
<h1>Editing article</h1>

<%= form_for :article, url: article_path(@article), method: :patch do |f| %>

  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% @article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>

<% end %>

<%= link_to 'Back', articles_path %>


f) Testing simple application CRUD




2.8. Adding some validation to simple RAILS application


  • Required fields and minimum limit of fields

$ vim app/models/article.rb
class Article < ApplicationRecord
  validates :title, presence: true, length: { minimum: 3 }
end



  • Capture new errors exceptions and show on top of page

$ vim app/views/articles/new.html.erb
     :
<%= form_for :article, url: articles_path  do |f| %>

  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% @article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
     :


2.9. Using form to clean up duplications on view

a. Backup 'new' and 'edit' form

$ cp app/views/articles/new.html.erb  app/views/articles/new.html.erb.without_form
$ cp app/views/articles/edit.html.erb app/views/articles/edit.html.erb.without_form


b. Create template '_form' to be included by 'new' and 'edit'

$ vim app/views/articles/_form.html.erb
<%= form_for @article do |f| %>

  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% @article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>

<% end %>


c. Change 'new' to include '_form'

$ vim app/views/articles/new.html.erb
<h1>New Article</h1>

<h1>New article</h1>

<%= render 'form' %>


<%= link_to 'Back', articles_path %>



d. Change 'edit' to include '_form'

$ vim app/views/articles/edit.html.erb
<h1>Editing article</h1>


<%= render 'form' %>


<%= link_to 'Back', articles_path %>



2.10. Adding second model detail of first master model

a. Create model comment detail of master article: one article has many comments

$  rails generate model Comment commenter:string body:text article:references
Running via Spring preloader in process 8667
      invoke  active_record
      create    db/migrate/20170318010912_create_comments.rb
      create    app/models/comment.rb
      invoke    test_unit
      create      test/models/comment_test.rb
      create      test/fixtures/comments.yml


b. Checking migrate scripts automatically generated by RAILS

$ cat db/migrate/*_create_comments.rb
$ cat app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :article
end
$ cat test/models/comment_test.rb
  :

$ cat test/fixtures/comments.yml
  :



c. Run migrate scripts on environment (DEVELOPMENT)

$  rails db:migrate
== 20170318010912 CreateComments: migrating ===================================
-- create_table(:comments)
   -> 0.1321s
== 20170318010912 CreateComments: migrated (0.1323s) ==========================


d. Associating models

$ vim app/models/article.rb
class Article < ApplicationRecord
  has_many :comments
  validates :title, presence: true, length: { minimum: 5 }
end


e. Adding routing for new model comments

$ vim config/routes.rb
Rails.application.routes.draw do
  get 'welcome/index'

  resources :articles do
    resources :comments
  end

  root 'welcome#index'

end


f. Generating a controller for detail new model Comment

$ rails generate controller Comments
$ cat app/controllers/comments_controller.rb # will be changed soon
class CommentsController < ApplicationController
end
$ ls -lad app/views/comments/
$ ls -la  test/controllers/comments_controller_test.rb
$ cat  app/helpers/comments_helper.rb
module CommentsHelper
end
$ cat app/assets/javascripts/comments.coffee 
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
$ cat app/assets/stylesheets/comments.scss
// Place all the styles related to the Comments controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/


g. Edit article view 'show' to let make a new details records of comments

$ vim app/views/articles/show.html.erb
<h1>Show Article</h1>

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>


h. Edit comments controller 'show' to implement 'create' and 'destroy'

$ vim app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end
  
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

$ vim app/views/articles/show.html.erb
<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>


i. Partial Comments to clean up 'show' with a template

$ cp app/views/articles/show.html.erb app/views/articles/show.html.erb.without_form
$ vim app/views/comments/_comment.html.erb
<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

<p>
  <%= link_to 'Destroy Comment', [comment.article, comment],
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>


j. Partial Form _form coments._form

$ vim app/views/comments/_form.html.erb
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>


k. Partial Form _form article.show

$ vim app/views/articles/show.html.erb
<h1>Show Article</h1>

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= render 'comments/form' %>

<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>


l. Adding method 'create' and 'destroy' to controller Comments

$ vim app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end

end


m. Deleting associated Objects

$ vim app/models/article.rb
class Article < ApplicationRecord
  has_many :comments, dependent: :destroy
  validates :title, presence: true, length: { minimum: 3 }
end


3. References



Nenhum comentário:

Postar um comentário