понеділок, 23 листопада 2009 р.

Співаємо з Sinatra!


Ця стаття є так би мовити третьою. У двох попередніх ми вже встигли познайомитися з веб-фреймворком Sinatra (Привіт Sinatra!) та ORM бібліотекою DataMapper (Привіт DataMapper!). Прийшов час застосувати ці знання на практиці. Напишемо наш "proof of concept" додаток, використовуючи Sinatra, DataMapper, HAML, SASS. Я вирішив, що ми проведемо наші навчальні заняття за написанням блогу.

Sinatra не піклується про те, як ви організовуєте ваш додаток. На відміну від Rails, Sinatra не накладає ряд серйозних обмежень на структуру ваших додатків. Ви можете покласти все в один файл, або розбити на структуру каталогів. Звичайно, якщо ви розіб'єте на окремі файли, потрібно підключати їх в міру необхідності - у Sinatra немає узгоджень, як в Rails, про те де шукати ці файли.
Моя структура каталогів, вона дуже проста:
/
  blog.rb
  db/
    db.sqlite3
  views/
    layout.haml
    /posts
      edit.haml
      index.haml
      new.haml
      show.haml

Я поклав все крім шаблонів в один файл. Таким чином конфігурації, моделі і всі події будуть у файлі blog.rb. Також Sinatra по замовчуванню підхоплює каталог views, який містить шаблони представлень.

Підключаємо необхідні бібліотеки у файлі blog.rb
require 'rubygems'
require 'sinatra'
require 'haml'
require 'dm-core'
require 'dm-timestamps'
require 'dm-migrations'

Конфігурація бази даних

Додамо наступні рядки в файл blog.rb:
DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/db.sqlite3")
Це налаштує DataMapper використовувати SQLite3 і збереже файл БД у каталозі db.

MVC

Концепція MVC дозволяє дозволяє розділити дані, представлення та обробку дій користувача на три окремі компоненти
  • Модель(Model). Надає дані (зазвичай для Viev), а також реагує на запити від контролера.
  • Представлення(View). Відповідає за представлення даних користувачеві.
  • Контролер(Controller). Інтерпретує дані, введені користувачем, та інформує модель і представлення про необхідність відповідної реакції.
Така внутрішня структура в цілому розбиває систему на самостійні частини і розподіляє відповідальність за різні компоненти. Важливо відзначити, що як представлення, так і контролер залежать від моделі. Тоді як модель не залежить ні від контролера, ні від представлення. Це одна з основних переваг такого поділу. Вона дозволяє будувати модель незалежно від візуального представлення, а також створювати кілька різних представлень для однієї моделі.


Модель

В Sinatra модель представляє з себе звичайний клас Ruby.
class Post
  include DataMapper::Resource

  property :id,         Serial
  property :title,      String
  property :body,       Text
  property :created_at, DateTime
  property :published,  Boolean, :default => false
end

Після опису Post пишемо:
DataMapper.auto_upgrade!
Цей рядок змусить DataMapper оновлювати структуру БД при змінах нашого класу Post.

Контролер

Одна з цікавих особливостей Sinatra, вам не потрібно створювати цілий клас контролера для простих додатків. Достатньо визначити обробники подій. Додамо необхідні події для нашої моделі Post. Для цього визначимо декілька методів-заглушок. Кожен метод відповідає за певну подію. Створимо базовий набір подій, яким повинен відповідати наш додаток для статей.


Напишемо реалізацію наших методів.
Метод index відображає весь список статей.
get '/posts' do
  @posts = Post.all
  haml :'posts/index'
end
Тут ми оголошуємо змінну @posts, яка буде доступна з шаблону, де ми і відобразимо наші статті. Вираз Post.all отримує всі статті з бази даних.

Метод show відображає одну статтю.
get '/posts/show/:id' do
  @post = Post.first(:id => params[:id])
  haml :'posts/show'
end
Post.first(id => params[:id]) говорить DataMapper знайти статтю відповідну id, переданого в params. params - це об'єкт-контейнер, який дозволяє передавати параметри виклику. Наприклад при виводі списку статей, ви можете клацнути на посилання певної статі і id цієї статті передасться в об'єкті params.

Метод new відображає HTML сторінку з формою для створення нової статті.
get '/posts/new' do
  haml :'posts/new'
end

Після заповнення полів форми на HTML сторінці, створюємо новий об'єкт в базі даних за допомогою методу create.
post '/posts/new' do
  @post = Post.new(:title => params[:title], :body => params[:body])
  if @post.save
    redirect '/posts'
  else
    redirect '/posts/new'
  end
end
Другий рядок створює новий екземпляр класу Post, заповнений даними, які надіслав користувач за допомогою форми. Наступним рядком ми намагаємося зберегти об'єкт і в разі успіху перенаправляємо на сторінку зі списком статей. Для цього використовується метод redirect, параметр якого встановлено в '/posts'. У випадку невдачі заново відображається сторінка створення нової статті.

Метод edit служить для редагування об'єкта, і дуже схожий на метод show - в обох виходить об'єкт із заданим id, різниця тільки в тому, що форма для show не редагується.
get '/posts/edit/:id' do
  @post = Post.first(:id => params[:id])
  haml :'posts/edit'
end

Метод update викликається після методу edit для оновлення існуючої статті. Цей метод схожий на create, але призначений для оновлення існуючого об'єкта в базі даних.
post '/posts/edit/:id' do
  id = params[:id]
  @post = Post.first(:id => id)
  if @post.update(:title => params[:title], :body => params[:body])
    redirect "/posts/show/#{id}"
  else
    redirect "/posts/edit/#{id}"
  end
end
Метод update схожий на save, але він не створює новий запис в БД, а тільки оновлює поля. У випадку успіху ми перенаправляємо на метод show, інакше відправляємо знову на редагування.

Реалізуємо метод destroy для видалення статті.
get '/posts/destroy/:id'
  post = Post.first(:id => params[:id])
  post.destroy!
  redirect '/posts'
end
У другому рядку ми знаходимо об'єкт по id і методом destroy! видаляємо його. Далі робимо перенаправлення на index.

Представлення

Створимо відповідні шаблони для візуального представлення даних користувачу.
Всі наші шаблони будуть використвувати один спільний макет. По замовчуванню Sinatra шукатиме його в views/layout.haml.

views/layout.haml
%html
  %head
    %title My Blog
    %meta{"http-equiv" => "Content-Type", :content => "text/html; charset=utf-8"}
  %body
    #container
      = yield


views/posts/index.haml
Відображає колекції статей, кожна з яких містить посилання на операцію іх отримання, оновлення та видалення. А також створення нової статті.
#header
  %h1 My blog

%a{:href => "posts/new"}> New Post
#content
  - @posts.each do |post|
    .container
      %h3= post.title.force_encoding('utf-8')
      %p= post.body.force_encoding('utf-8')
      %p= post.created_at

      %a{:href => "posts/show/#{post.id}"}> Show
      |
      %a{:href => "posts/edit/#{post.id}"}> Edit
      |
      %a{:href => "posts/destroy/#{post.id}"}> Delete


views/posts/new.haml
Відображає порожню форму для додавання нової статті.
%h1 Write a new post

%form{:method => 'post', :action => "/posts/new"}
  %ul
    %li#title
      %label{:for => 'post_title'} Title:
      %br
      %input{:type=>'text', :id => 'post_title', :name => 'title'}
    %li#body
      %label{:for => 'post_body'} Body:
      %br
      %textarea{:type=>'textarea', :id => 'post_body', :cols => 80, :rows => 5, :name => 'body'}
  %input{:type=>'submit', :value => 'New'}

%a{:href => "/posts"}= "< Back"


views/posts/show.haml
Відображає поточну статтю.
%h3= @post.title
%p= @post.body

%a{:href => "/posts"}= "< Back"



views/posts/edit.haml
Відображає форму, заповнену даними поточної статті.
%h1 Edit a post

%form{:method => 'post', :action => "/posts/edit/#{@post.id}"}
  %ul
    %li#title
      %label{:for => 'post_title'} Title:
      %br
      %input{:type=>'text', :id => 'post_title', :name => 'title', :value => @post.title}
    %li#body
      %label{:for => 'post_body'} Body:
      %br
      %textarea{:type=>'textarea', :id => 'post_body', :cols => 80, :rows => 5, :name => 'body'}= @post.body
  %input{:type=>'submit', :value => 'Edit'}

%a{:href => "/posts"}= "< Back"



У наступній статті я продовжу розповідь про організацію блогу.
Дякую за увагу!

Немає коментарів: