- PVSM.RU - https://www.pvsm.ru -
В определенный момент пришло моё время написать что-то отдаленно похожее на web сайт. Самый обычный сайт: главная страница с отображением на ней трёх табличек из БД и парой форм для заполнения оных контентом. Такие мной были поставлены начальные требовнаия. Думаю, у каждого при написании первого сайта возникает вопрос: какие инструменты для этого использовать? Для меня были довольно принципиальны критерии:
Соответственно, взгляд упал на Ruby on Rails: это полноценный framework, довольно популярный и, как ни странно, содержит в себе Ruby.
И принялся я его изучать [1], чего и вам желаю. Хороший гайд: помогает понять, что рельсы — это гигантская сборка gem'ов, которые потом ещё и разворачивать придется на passenger. То есть, кроме мороки с bundler'ом или rvm (чего в итоге не избежать), придется ещё и passenger стыковать с Apache или nginx. Меня эти перспективы напугали и, дочитав таки tutorial, я начал искать чем бы RoR заменить, оставив при этом от него только необходимое. Для меня все ограничилось Ruby и ActiveRecord. Первые поиски пути выполнения Ruby кода на Apache показали, что есть mod_ruby: этот [2] и этот [3].
Вот на это API и пишутся модули, позвоялющие выполнять скрипты PHP, Python и Ruby. Это не CGI, что сулит повышенную производительность. Вот какие модули я имею ввиду.
Однако, первые же опыты привели меня к такой [4] и вот такой [5] ситуации.
Конечно, не бог весть какие проблемы: всё решаемо, но тут я посмотрел на даты последних коммитов, увидел заветное "пол года назад" и отказался от этой идей.
После этого мой выбор пал на FCGI как довольно перспективное продолжение CGI. Почему не CGI? А потому-что [6]. Т.е. для FCGI у Ruby есть gem, который позволяет обрабатывать запросы не в сыром виде CGI, а посредством интерфейса гема. Смотрится удобно [7], но об этом позже. Ну вроде всё: есть Apache, есть FCGI, Ruby… И, так как у меня есть небольшой backend в виде БД, а работать с тяжелым mysql или аналогами не хотелось, решил я прихватить ActiveRecord [8] из RoR себе в виде файла sqlite3 БД.
Заинтересовало? Добро пожаловать под кат.
Составив такой стек, выделил я себе серверок с CentOS и приступил к воплощению идей в жизнь.
Всё начинается с Web сервера. В моём случае всё оказалось довольно просто: yum install epel-release; yum install apache mod_fcgid fcgi-devel mod_ssl gnutls-utils
.
Такая портянка пакетов обусловлена нашей жаждой оспользовать gem ruby-fcgi… И, конечно тем, что почти все пакеты в EPEL. По старой админской привычке я пропарсил конфиг Apache… И нашел там кучу совершенно бесполезных модулей, чего и вам советую!
<VirtualHost mysite:443>
#common options
ServerName mysite:443
#loging
LogLevel info
ErrorLog logs/mysite-error_log
CustomLog logs/mysite-access_log common
#main dir
DocumentRoot /var/www/html/
<Directory /var/www/html/>
Options ExecCGI
DirectoryIndex index.rb.fcgi
AllowOverride None
#Access
Allow from all
#LDAP
AuthLDAPUrl <>
AuthLDAPBindDN <>
AuthLDAPBindPassword <>
#Authorization
AuthType Basic
AuthBasicProvider ldap
AuthName "Input your domain login name and password"
Require ldap-attribute <>
Require ldap-attribute <>
</Directory>
#Scripts timeouts
FcgidIOTimeout 300
#SSL
SSLEngine on
SSLProtocol TLSv1
SSLCertificateFile /var/www/html/certs/ca_cert.pem
SSLCertificateKeyFile /var/www/html/certs/ca_key.pem
</VirtualHost>
<> помечены опущенные параметры
Само собой, имя mysite прописано в DNS сервере, mod_ssl установлен.
Как вы должны были заметить, вместе с Apache мы ставим mod_fcgid, который и есть интерфейс для встраивания скриптов для обработки запросов.
При установке этот модуль создал конфиг в /etc/httpd/conf.d/fcgid, содержащий:
LoadModule fcgid_module modules/mod_fcgid.so
AddHandler fcgid-script fcg fcgi fpl
FcgidIPCDir /var/run/mod_fcgid
FcgidProcessTableFile /var/run/mod_fcgid/fcgid_shm
Интерес здесь представляет только строка AddHandler fcgid-script fcg fcgi fpl
, которую неплохо бы из соображений безпасности заменить на AddHandler rb.fcgi
.
Так мы ограничиваем исполнение FCGID только скриптов с расширением rb.fcgi.
Само собой, надо ставить Ruby. Но не в коем случае не из репозитория: многие печали буду. А конкретнее: придется прописывать полные пути до gem'ов при их подключении.
А потом перепрописывать при обновлении. Так что ставим RVM:
sudo su -
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
curl -sSL https://get.rvm.io | bash -s stable --ruby=2.2.1
source /etc/profile.d/rvm.sh
После выполнения этого в системе появится rvm и ruby… и ещё кое что.
Дело дошло до настройки FCGI. Если как обычно, то gem install fcgi
. Только в этом случае gem установится в текущий gemset в rvm. А для того, чтобы в скриптах, которые будет выполнять Apache, gem'ы были видны, надо устанавливать их в глобальный gemset. То есть, rvm gemset use global; gem install fcgi
. И далее, если я говорю "устанавливаем gem", то имеется в виду нечто подобное.
Буквально это нам даёт право писать require "sqlite3"
в FCGI скриптах для Apache. Однако, интерпретатор в скриптах по-прежнему придется указывать #!/usr/local/rvm/rubies/ruby-2.2.1/bin/ruby
— мне этого не удалось избежать.
Теперь, потерев руки, можно делать файл /var/www/html/index.rb.fcgi
и писать в него чтото типа:
#!/usr/local/rvm/rubies/ruby-2.2.1/bin/ruby
require "fcgi"
FCGI.each { |request|
case request.env["REQUEST_METHOD"]
when 'GET'
request.out.print "Content-Type: text/htmlnStatus: 200 ОКnn<!DOCTYPE html><html><h3 align=center style="color: green"><strong>Success</strong></h3></html>"
else
request.out.print "Content-Type: text/htmlnStatus: 403 Forbiddennn<!DOCTYPE html><html><h3 align=center style="color: red"><strong>Access denied</strong></h3></html>"
end
request.finish
}
Довольно простой скрипт: выдает страничку с 'Success' на GET запрос и страничку с 'Access denied' на любой другой.
Т.е. fcgi gem предоставляет нам класс FCGI, который, будучи использованный в FCGI скриптах, может принимать запросы самостоятельно.
Мной это изучено по документации [9] проекта.
Мы получаем при запросе объект request, который имеет:
in и out — обычные объекты типа IO [10], что какбы намекает [11].
Одна из задач выполнена: мы получаем чистый Ruby для разработки сайтов. Можно использовать все, что только есть в интернетах, для генерации html.
Вот тут, внезапно, пронадобилось использовать БД как backend. И, раз уж мы на Ruby, воспользуемся Rails фишкой в виде ActiveRecord.
Не скрою, я пользовался этой [12] статьёй, но у меня есть что добавить. А, дабы не путать вас в ссылках на неё, опишу всё по порядку.
Устанавливаем gem: gem install activerecord sqlite3
— для работы с sqlite3 адаптером БД.
Для работы с БД необходимо 'создать соединение' с БД. Это выглядит както так:
require 'active_record'
require 'sqlite3'
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:database => "/var/www/html/db/mysite.db"
)
Ничего сверхъестественного и ничего нового они не превносят — просто указание на расположение БД и её формат. Эти строки надо добавить при инициализации любого fcgi скрипта для подключения в его окружение БД.
Миграции (в терминологии Rails) — это метод автоматизаци разворачивания схемы БД при разработке, тестировании, внедрении,… По сути, это скрипты, которые запускаются для создания другими людьми БД, используемой сайтом.
Пример такого скрипта:
#!/usr/local/rvm/rubies/ruby-2.2.1/bin/ruby
require 'active_record'
require 'sqlite3'
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:database => "/var/www/html/db/mysite.db"
)
class CreateUsers < ActiveRecord::Migration
def up
create_table :users do |t|
t.string :name
t.string :email
t.string :group
t.timestamps null: false
end
end
end
CreateUsers.migrate(:up)
Этот скрипт создает таблицу users, содержащую 5 полей — id, name, email, group и timestamp.
Существует довольно много действий, осуществимых с БД путем миграций, но не будем заострять на этом внимание [13]. Все умеют читать мануалы. А для нас главное понять, что для создания и модификации БД необходимо написать ряд скриптов, а не носить везде с собой файл sqlite со схемой. Эти файлы никуда не включаются — это отдельная часть проекта и при работе сайта она не используется.
Казалось бы, БД есть — почему бы не использовать её. Но не все так просто: неплохо бы определиться с тем, что наша БД может содержать, а что категорически нет.
Это подразуммевает, что у нас на руках есть схема таблиц БД с обозначенными полями, их типами, допустимыми значениями и связями между ними.
Здесь и начинается то, что упускает большинство руководств по использванию ActiveRecord. Не буду томить, скрипт валидаций:
class Host < ActiveRecord::Base
belongs_to :user, :inverse_of => :hosts, :validate => true
validates :address, :presence => true, :uniqueness => true
validates :user_id, :presence => true
validate :address_and_user_should_exists,
def address_and_user_should_exists
if Address.find_by_id(address_id) == nil
errors.add(:address_id, "should points at exist address")
end
if User.find_by_id(user_id) == nil
errors.add(:user_id, "should points at exist user")
end
end
validates :name, length: { minimum: 2, maximum: 20 }, presence: true, :format => { :with => /S(S|-)*S[^z]/i }, :uniqueness => true
validates :purpose, length: { minimum: 4, maximum: 100 }, presence: true, :format => { :with => /[^$^&`]+/i }
validates :description, length: { maximum: 700 }, presence: false, :format => { :with => /[^$^&`]*/i }
end
class User < ActiveRecord::Base
has_many :hosts, :inverse_of => :user, :dependent => :destroy
def self.valid_groups
["test", "probe" "etc"]
end
validates :name, length: { minimum: 2, maximum: 30 }, presence: true, :format => { :with => /S(S| )*S[^z]/i }, :uniqueness => true
validates :email, length: { minimum: 5, maximum: 40 }, presence: true, :format => { :with => /A[w+-.]+@[a-zd-]+(.[a-z]+)*.[a-z]+z/i }
validates :group, length: { minimum: 2, maximum: 30 }, presence: true, :format => { :with => /(w|-)+/i },
:inclusion => { :in => valid_groups, :message => "%{value} is not a valid group. Select one from drop-down hint."}
end
require и подключение БД опущены
Он предназначен для определения допустимых значений, записываемых в БД. В приведенном скрипте:
has_many
и belongs_to
Начнем разбор всего, что написано в валидаторе. Строка class User < ActiveRecord::Base
означает, что в окружении у нас теперь есть класс User, который представляет собой (связан с) таблицей users БД, которую мы где-то выше подключили. Так, вызов User.find_by_id
позволяет производить поиск записей по id в таблице. И ничего больше. То есть, никакой магии нет — просто возвращается запись, если она есть.
Далее в обоих классах идёт строка, описывающая связи между таблицами.
Например, has_many :hosts, :inverse_of => :user, :dependent => :destroy
означает, что:
Здесь и кроется основная тайна ActiveRecord: связи AR не являются связями в БД. В БД их попросту нет.
То есть, при использовании связи has_one не следует ожидать, что AR будет отслеживать соблюдение отношения 1:1 при добавлении записей: это на вашей совести.
А ActiveRecord только гарантирует, что объявление связи позволит находить связанные записи.
И, наконец, идёт список валидаторов полей:
validates :user_id, :presence => true
validate :address_and_user_should_exists,
def address_and_user_should_exists
if Address.find_by_id(address_id) == nil
errors.add(:address_id, "should points at exist address")
end
if User.find_by_id(user_id) == nil
errors.add(:user_id, "should points at exist user")
end
end
validates :name, length: { minimum: 2, maximum: 20 }, presence: true, :format => { :with => /S(S|-)*S[^z]/i }, :uniqueness => true
Именно здесь, к примеру, реализуется то, что для указанных связанных полей должны существовать соответствующие записи в таблицах.
Думаю, значение кода понятно. если нет, то вот [14] на мой вкус наилучшее средство.
Там можно узнать как пользоваться валидаторами, в том числе и в своих коварных целях.
К чему я веду: ActiveRecord упрощает жизнь тем, что не приходится иметь дело с sql синтаксисом или чем-то подобным. Однако, вам придется реализовать всю логику работы схемы БД самостоятельно.
Это неплохо: можно проверить что угодно на соотвестсвие чему угодно при добавлении, изменении и удалении записей БД. Но вся эта реализация на совести разработчика.
После написания всех упомянутых выше вещей мы получаем:
User.find_by(:name => "Vasya")
, User.first.name
, User.all.each {...}
User.create(:name => "Oleg", ...)
, User.first.destroy
User.first.hosts
(причем, если связь 1:1, то было бы User.first.host
). Поконкретнее про связи здесь [15]. На выходе мы получили исполнение Ruby скриптов по запросам Apache с вкраплениями ActiveRecord. В такой стек хорошо вписывается MVC из RubyOnRails путем использования валидаций как модели, Ruby + FCGI как контроллера и Ruby генераторов html как представлений. Надо ли приводить гайд на структуру проектов на основе вышеописанного покажет время.
Спасибо за внимание.
Автор: deman_killer
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ruby/113622
Ссылки в тексте:
[1] изучать: http://railstutorial.ru
[2] этот: https://github.com/shugo/mod_ruby
[3] этот: https://github.com/mikeowens/mod_ruby
[4] такой: https://github.com/shugo/mod_ruby/issues/5
[5] вот такой: https://github.com/mikeowens/mod_ruby/issues/1
[6] потому-что: https://github.com/saks/ruby-fcgi
[7] Смотрится удобно: https://github.com/saks/ruby-fcgi#sample
[8] ActiveRecord: https://github.com/rails/rails/tree/master/activerecord
[9] документации: http://www.rubydoc.info/gems/fcgi/0.9.2.1
[10] IO: http://ruby-doc.org/core-2.0.0/IO.html
[11] намекает: http://ruby-doc.org/core-2.0.0/IO.html#method-c-readlines
[12] этой: https://habrahabr.ru/post/98751/
[13] внимание: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
[14] вот: http://guides.rubyonrails.org/active_record_validations.html
[15] здесь: http://guides.rubyonrails.org/association_basics.html
[16] Источник: https://habrahabr.ru/post/278195/
Нажмите здесь для печати.