Ruby on Rails — это полная платформа для создания полнофункционального веб-приложения, используемого многими крупными организациями. В этом сообщении блога я собираюсь провести вас через создание API с Ruby on Rails и использование API с ванильным JavaScript FrontEnd.

Бэкэнд

Создание API с помощью Rails теперь является непосредственным гражданином Rails 5, что означает, что вы можете передать флаг —API и удалить многие шаблоны и получить все, что вам нужно только для API. Начало работы с нашим проектом. ReadIt — это простая платформа, которая позволяет пользователям отправлять интересные истории, которые они нашли в Интернете.

rails new readit — api

Это сгенерирует шаблон приложения Ruby on rails, которое является специфичным для API, затем в Gemfile я раскомментирую «rack-cors», а также добавлю фейкер для создания поддельного пользовательского контента для наших начальных данных. Пробег bundle install

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.6.1'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.2', '>= 6.0.2.1'
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Use Active Storage variant
# gem 'image_processing', '~> 1.2'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'
gem 'faker'
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

В папке config › Initializer cors.rb обновите файл следующим образом, чтобы избежать CORS

# Be sure to restart your server when you modify this file.
# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
# Read more: https://github.com/cyu/rack-cors
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

В этом приложении у нас есть три модели User, Content и Comment. Использование генератора рельсов

rails g resource user first_name:string last_name:string email:string

Это создаст пользовательский ресурс, который включает модель, контроллер и маршруты, мы можем сделать это для оставшихся двух ресурсов.

rails g resource content title:string url:string image:string description:string user_id:integer
rails g resource comment comment_text:string user:references conntent:references

Миграция

Когда вы запускаете миграцию с помощью rails db:migrate, у вас есть схема ниже

ActiveRecord::Schema.define(version: 2020_01_29_110445) do
create_table "comments", force: :cascade do |t|
    t.integer "user_id", null: false
    t.integer "content_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.string "comment_text"
    t.index ["content_id"], name: "index_comments_on_content_id"
    t.index ["user_id"], name: "index_comments_on_user_id"
  end
create_table "contents", force: :cascade do |t|
    t.string "url"
    t.string "description"
    t.string "image"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.integer "user_id"
    t.string "title"
  end
create_table "users", force: :cascade do |t|
    t.string "first_name"
    t.string "last_name"
    t.string "email"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
add_foreign_key "comments", "contents"
  add_foreign_key "comments", "users"
end

Модель

class User < ApplicationRecord
    validates :first_name,  presence: true, length: { maximum: 50 }
    validates :last_name,  presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 255 },
                      format: { with: VALID_EMAIL_REGEX },
                      uniqueness: true
has_many :contents
    has_many :comments, through: :contents
end
class Content < ApplicationRecord
    validates :url,  presence: true
    validates :description,  presence: true
    validates :image,  presence: true
    validates :title,  presence: true
belongs_to :user
    has_many :comments
end
class Comment < ApplicationRecord
  validates :content,  presence: true
  
  belongs_to :user
  belongs_to :content
end

Выше я добавил проверку и отношения между моделями.

Контроллер

Контроллер контента для рендеринга JSON

class ContentsController < ApplicationController
    def index
        contents = Content.all
        render json: contents, include: [:user]
    end
def show
        content = Content.find(params[:id])
        if content
            render json: {
                title: content.title,
                url: content.url,
                image: content.image,
                description: content.description,
                user: content.user,
                comment: content.comments
}
        else
            render json: {message: "Can't find the content"}
        end
    end
def create
        content = Content.new
        content.title = params[:title]
        content.url = params[:url]
        content.description = params[:description]
        content.image = params[:image]
        content.user_id = 1
        content.save
        if content.save
            render json: content
        else
            render json: {message: "Error creating a content"}
        end
    end
def destroy
        content = Content.find(params[:id])
        if content
            content.destroy
            render json: {message: "Deleted succesfully"}
        else
            render json: {message: "Can't find this content"}
        end
end
private
def content_params
        params.permit(:title, :url, :image, :description)
    end
end

Контроллер комментариев

class CommentsController < ApplicationController
    def index 
        comments = Comment.all 
        render json: comments
    end
def create 
        content = Content.find_by(id: params[:id])
        comment = Comment.new 
        comment.content = params[:content]
        comment.user_id = 1
        comment.content_id = content.id
        comment.save
if content.save 
            render json: comment
        else
            render json: {message: "Error creating a comment"}
        end
    end
end

Наш API готов к тестированию. Используя postman, запуская сервер с помощью «rails s» и отправляя запрос на получение по адресу 127.0.0.1:3000/contents, мы получаем этот ответ обратно.

[
  {
    "id": 1,
    "url": "https://www.huffingtonpost.co.uk/entry/mistakes-tourists-london_l_5e13ccdae4b0b2520d26a1d0?guccounter=1&guce_referrer=aHR0cHM6Ly9kdWNrZHVja2dvLmNvbS8&guce_referrer_sig=AQAAAGIQ8E4Q6e_i9Xa11YKNo3s-wQ53iYiHu5HEQ5wNyamqQVT35zhQKHL4ELbPspyMJzo5p8mOyzLYXCcqq0iDXujD08Z4a_Q9-Jg8sTYozjSqwz3tHlxBnrw83KMIlxBHs7A3NuA4E120NRKO4grFFbYyDscPJJObuJlzsFQNVaj6",
    "description": "The London Bridge that still stands today dates from 1973. So, despite the fact London Bridge has existed here the longest, the actual bridge standing today is one of the more modern bridges over the Thames in London. Tower Bridge – Tower Bridge was opened in 1894 making this a purely Victorian bridge. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/London_Bridge_Illuminated.jpg/800px-London_Bridge_Illuminated.jpg",
    "created_at": "2020-01-28T05:45:10.460Z",
    "updated_at": "2020-01-28T05:45:10.460Z",
    "user_id": 1,
    "title": "25 Mistakes Tourists Make While Visiting London",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 2,
    "url": "https://www.pariscityvision.com/en/paris/landmarks/eiffel-tower/history",
    "description": "The Eiffel Tower French: tour Eiffel is a wrought-iron lattice tower on the Champ de Mars in Paris, France. It is named after the engineer Gustave Eiffel, whose company designed and built the tower. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/85/Tour_Eiffel_Wikimedia_Commons_%28cropped%29.jpg/800px-Tour_Eiffel_Wikimedia_Commons_%28cropped%29.jpg",
    "created_at": "2020-01-28T05:45:10.465Z",
    "updated_at": "2020-01-28T05:45:10.465Z",
    "user_id": 1,
    "title": "Eiffel Tower History",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 3,
    "url": "https://www.chinahighlights.com/greatwall/",
    "description": "The Great Wall of China (Chinese: 萬里長城; pinyin: Wànlǐ Chángchéng) is the collective name of a series of fortification systems generally built across the historical northern borders of China to protect and consolidate territories of Chinese states and empires against various nomadic groups of the steppe and their polities. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/The_Great_Wall_of_China_at_Jinshanling-edit.jpg/1280px-The_Great_Wall_of_China_at_Jinshanling-edit.jpg",
    "created_at": "2020-01-28T05:45:10.470Z",
    "updated_at": "2020-01-28T05:45:10.470Z",
    "user_id": 1,
    "title": "The Great Wall of China",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 4,
    "url": "https://victoriafallstourism.org/",
    "description": "David Livingstone, the Scottish missionary and explorer, is believed to have been the first European to view Victoria Falls on 16 November 1855, from what is now known as Livingstone Island, one of two land masses in the middle of the river, immediately upstream from the falls near the Zambian shore. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0b/Cataratas_Victoria%2C_Zambia-Zimbabue%2C_2018-07-27%2C_DD_07.jpg/1280px-Cataratas_Victoria%2C_Zambia-Zimbabue%2C_2018-07-27%2C_DD_07.jpg",
    "created_at": "2020-01-28T05:45:10.475Z",
    "updated_at": "2020-01-28T05:45:10.475Z",
    "user_id": 1,
    "title": "Victoria Falls",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 5,
    "url": "https://www.huffingtonpost.co.uk/entry/mistakes-tourists-london_l_5e13ccdae4b0b2520d26a1d0?guccounter=1&guce_referrer=aHR0cHM6Ly9kdWNrZHVja2dvLmNvbS8&guce_referrer_sig=AQAAAGIQ8E4Q6e_i9Xa11YKNo3s-wQ53iYiHu5HEQ5wNyamqQVT35zhQKHL4ELbPspyMJzo5p8mOyzLYXCcqq0iDXujD08Z4a_Q9-Jg8sTYozjSqwz3tHlxBnrw83KMIlxBHs7A3NuA4E120NRKO4grFFbYyDscPJJObuJlzsFQNVaj6",
    "description": "The London Bridge that still stands today dates from 1973. So, despite the fact London Bridge has existed here the longest, the actual bridge standing today is one of the more modern bridges over the Thames in London. Tower Bridge – Tower Bridge was opened in 1894 making this a purely Victorian bridge. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/London_Bridge_Illuminated.jpg/800px-London_Bridge_Illuminated.jpg",
    "created_at": "2020-01-28T05:48:12.272Z",
    "updated_at": "2020-01-28T05:48:12.272Z",
    "user_id": 1,
    "title": "25 Mistakes Tourists Make While Visiting London",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 6,
    "url": "https://www.pariscityvision.com/en/paris/landmarks/eiffel-tower/history",
    "description": "The Eiffel Tower French: tour Eiffel is a wrought-iron lattice tower on the Champ de Mars in Paris, France. It is named after the engineer Gustave Eiffel, whose company designed and built the tower. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/85/Tour_Eiffel_Wikimedia_Commons_%28cropped%29.jpg/800px-Tour_Eiffel_Wikimedia_Commons_%28cropped%29.jpg",
    "created_at": "2020-01-28T05:48:12.277Z",
    "updated_at": "2020-01-28T05:48:12.277Z",
    "user_id": 1,
    "title": "Eiffel Tower History",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 7,
    "url": "https://www.chinahighlights.com/greatwall/",
    "description": "The Great Wall of China (Chinese: 萬里長城; pinyin: Wànlǐ Chángchéng) is the collective name of a series of fortification systems generally built across the historical northern borders of China to protect and consolidate territories of Chinese states and empires against various nomadic groups of the steppe and their polities. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/The_Great_Wall_of_China_at_Jinshanling-edit.jpg/1280px-The_Great_Wall_of_China_at_Jinshanling-edit.jpg",
    "created_at": "2020-01-28T05:48:12.280Z",
    "updated_at": "2020-01-28T05:48:12.280Z",
    "user_id": 1,
    "title": "The Great Wall of China",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 8,
    "url": "https://victoriafallstourism.org/",
    "description": "David Livingstone, the Scottish missionary and explorer, is believed to have been the first European to view Victoria Falls on 16 November 1855, from what is now known as Livingstone Island, one of two land masses in the middle of the river, immediately upstream from the falls near the Zambian shore. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0b/Cataratas_Victoria%2C_Zambia-Zimbabue%2C_2018-07-27%2C_DD_07.jpg/1280px-Cataratas_Victoria%2C_Zambia-Zimbabue%2C_2018-07-27%2C_DD_07.jpg",
    "created_at": "2020-01-28T05:48:12.284Z",
    "updated_at": "2020-01-28T05:48:12.284Z",
    "user_id": 1,
    "title": "Victoria Falls",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 9,
    "url": "https://www.huffingtonpost.co.uk/entry/mistakes-tourists-london_l_5e13ccdae4b0b2520d26a1d0?guccounter=1&guce_referrer=aHR0cHM6Ly9kdWNrZHVja2dvLmNvbS8&guce_referrer_sig=AQAAAGIQ8E4Q6e_i9Xa11YKNo3s-wQ53iYiHu5HEQ5wNyamqQVT35zhQKHL4ELbPspyMJzo5p8mOyzLYXCcqq0iDXujD08Z4a_Q9-Jg8sTYozjSqwz3tHlxBnrw83KMIlxBHs7A3NuA4E120NRKO4grFFbYyDscPJJObuJlzsFQNVaj6",
    "description": "The London Bridge that still stands today dates from 1973. So, despite the fact London Bridge has existed here the longest, the actual bridge standing today is one of the more modern bridges over the Thames in London. Tower Bridge – Tower Bridge was opened in 1894 making this a purely Victorian bridge. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/London_Bridge_Illuminated.jpg/800px-London_Bridge_Illuminated.jpg",
    "created_at": "2020-01-28T05:49:51.519Z",
    "updated_at": "2020-01-28T05:49:51.519Z",
    "user_id": 1,
    "title": "25 Mistakes Tourists Make While Visiting London",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 10,
    "url": "https://www.pariscityvision.com/en/paris/landmarks/eiffel-tower/history",
    "description": "The Eiffel Tower French: tour Eiffel is a wrought-iron lattice tower on the Champ de Mars in Paris, France. It is named after the engineer Gustave Eiffel, whose company designed and built the tower. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/8/85/Tour_Eiffel_Wikimedia_Commons_%28cropped%29.jpg/800px-Tour_Eiffel_Wikimedia_Commons_%28cropped%29.jpg",
    "created_at": "2020-01-28T05:49:51.524Z",
    "updated_at": "2020-01-28T05:49:51.524Z",
    "user_id": 1,
    "title": "Eiffel Tower History",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 11,
    "url": "https://www.chinahighlights.com/greatwall/",
    "description": "The Great Wall of China (Chinese: 萬里長城; pinyin: Wànlǐ Chángchéng) is the collective name of a series of fortification systems generally built across the historical northern borders of China to protect and consolidate territories of Chinese states and empires against various nomadic groups of the steppe and their polities. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/The_Great_Wall_of_China_at_Jinshanling-edit.jpg/1280px-The_Great_Wall_of_China_at_Jinshanling-edit.jpg",
    "created_at": "2020-01-28T05:49:51.528Z",
    "updated_at": "2020-01-28T05:49:51.528Z",
    "user_id": 1,
    "title": "The Great Wall of China",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 12,
    "url": "https://victoriafallstourism.org/",
    "description": "David Livingstone, the Scottish missionary and explorer, is believed to have been the first European to view Victoria Falls on 16 November 1855, from what is now known as Livingstone Island, one of two land masses in the middle of the river, immediately upstream from the falls near the Zambian shore. - Wikipedia",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0b/Cataratas_Victoria%2C_Zambia-Zimbabue%2C_2018-07-27%2C_DD_07.jpg/1280px-Cataratas_Victoria%2C_Zambia-Zimbabue%2C_2018-07-27%2C_DD_07.jpg",
    "created_at": "2020-01-28T05:49:51.533Z",
    "updated_at": "2020-01-28T05:49:51.533Z",
    "user_id": 1,
    "title": "Victoria Falls",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  },
  {
    "id": 14,
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/London_Bridge_Illuminated.jpg/800px-London_Bridge_Illuminated.jpg",
    "description": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/London_Bridge_Illuminated.jpg/800px-London_Bridge_Illuminated.jpg",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/London_Bridge_Illuminated.jpg/800px-London_Bridge_Illuminated.jpg",
    "created_at": "2020-01-31T16:40:17.088Z",
    "updated_at": "2020-01-31T16:40:17.088Z",
    "user_id": 1,
    "title": "Sample",
    "user": {
      "id": 1,
      "first_name": "Peter",
      "last_name": "Ayeni",
      "email": "[email protected]",
      "created_at": "2020-01-28T05:45:10.437Z",
      "updated_at": "2020-01-28T05:45:10.437Z"
    }
  }
]

Наш API завершен, мы можем двигаться вперед с внешним интерфейсом.

Для внешнего интерфейса у меня есть такая структура папок.

› Asset
-CSS › style.css
- JS › index.js
index.html

----- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Top Stories from around the web</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="./assets/css/style.css" >
</head>
<body>
<main role="main" id="main">
</main>
<script src="./assets/js/index.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>

Весь файл JavaScript находится в index.js, и каждая HTML-страница динамически генерируется из JavaScript. Я использую Fetch API для вызова AJAX на серверную часть.

const main = document.querySelector("#main");
// Add form
const formHTML = () => {
let formHtml = `
<section class="jumbotron text-center">
<div class="container">
<h1>ReadIt</h1>
<p class="lead text-muted">Curated top stories from around the web</p>
<form class="form-content">
<img class="mb-4" src="/docs/4.4/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
<h2 class="h3 mb-3 font-weight-normal">Add a story</h2>
<div class="form-group">
<label for="inputEmail" class="sr-only">Title</label>
<input type="text" id="inputTitle" class="form-control" placeholder="Title" required autofocus>
</div>
<div class="form-group">
<label for="inputURL" class="sr-only">Story URL</label>
<input type="url" id="inputURL" class="form-control" placeholder="URL" required>
</div>
<div class="form-group">
<label for="inputImage" class="sr-only">Image URL</label>
<input type="url" id="inputImage" class="form-control" placeholder="Image URL" required>
</div>
<div class="form-group">
<label for="description" class="sr-only">Description</label>
<textarea class="form-control" id="description" rows="3" placeholder="Description"></textarea>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Submit</button>
</form>
</div>
</section>
<section class="contents">
<div class="album py-5 bg-light">
<div class="container">
<div class="row">
</div>
</div>
</div>
</section>
`;
main.innerHTML += formHtml;
};
const contentCard = content => {
return `
<div class="col-md-4">
<div class="card mb-4 shadow-sm">
<img  class="bd-placeholder-img card-img-top" src="${content.image}"/>
<div class="card-body">
<h3>${content.title}</h3>
<p class="card-text">${content.description}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<a class="btn btn-sm btn-outline-secondary" href="${content.url}" target="_blank">View Story from Source</a>
</div>
<small class="text-muted">By: ${content.user.first_name}</small>
</div>
</div>
</div>
</div>
`;
};
// append card to HTML
const displayCard = content => {
const contentRow = document.querySelector(".row");
contentRow.innerHTML += contentCard(content);
};
// Get contents
const getContents = () => {
fetch("http://localhost:3000/contents")
.then(resp => resp.json())
.then(contents => {
contents.forEach(content => displayCard(content));
});
};
// Add content
const addContent = () => {
const addContentForm = document.querySelector(".form-content");
addContentForm.addEventListener("submit", e => {
e.preventDefault();
let title = document.querySelector("#inputTitle").value;
let url = document.querySelector("#inputURL").value;
let image = document.querySelector("#inputImage").value;
let description = document.querySelector("#description").value;
const contentRow = document.querySelector(".row");
let htmlCard = `
<div class="col-md-4">
<div class="card mb-4 shadow-sm">
<img  class="bd-placeholder-img card-img-top" src="${image}"/>
<div class="card-body">
<h3>${title}</h3>
<p class="card-text">${description}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<a class="btn btn-sm btn-outline-secondary" href="${url}" target="_blank">View Story from Source</a>
</div>
</div>
</div>
</div>
</div>
`;
contentRow.innerHTML += htmlCard;
fetch("http://localhost:3000/contents", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
title: title,
url: url,
image: image,
description: description
})
})
.then(resp => resp.json())
.then(content => contentCard(content));
addContentForm.reset();
});
};
// delete content
// Like Images
const deleteContent = contentId => {
data = {
content_id: contentId
};
return fetch("http://localhost:3000/contents", {
method: "DELETE",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}).then(console.log);
};
formHTML();
getContents();
addContent();

Полный исходный код можно найти по адресу https://github.com/peterayeniofficial/js-rails-readit.

Спасибо