Upload
anatoly-sharifulin
View
5.941
Download
5
Embed Size (px)
DESCRIPTION
YAPC::Russia "May Perl" 2011http://event.perlrussia.org/mayperl4/talk/130
Citation preview
Почему Mojolicious?Анатолий Шарифулин
YAPC::Russia 2011
mojolicio.us
Кто знает, что такое Mojolicious?
Кто использует Mojolicious?
На кого повлиял я? :)
Коротко
Современный и молодой
веб-фреймворк
Веб в коробке!
Mojo::BaseMojo::DOM, Mojo::JSON
Mojo::UserAgentMojo::IOLoopMojo::Template
MojoMojolicious
Mojolicious::Lite
Test::Mojoojo...
385 вотчеров88 форковПо данным github.com
и один Шарифулин :-)По моему мнению
Почему всё-таки Mojolicious?
Когда есть Dancer, Plack, Python и Node.js
Помогает решить почти любую задачу
Почему Mojolicious?
Начиная от простого сайта в 5 страниц
Почему Mojolicious?
tochkak.ru
use Mojolicious::Lite;
get '/' => 'about/what' ;get '/who' => 'about/who' ;get '/what' => 'about/what' ;get '/where' => 'about/where';get '/code' => 'about/code' ;
app->log->level('error');
app->start;
Заканчивая стартапом c 5000+ пользователями
Почему Mojolicious?
frodio.com
или узкоспециализированнойсистемой управлениядля кинотеатров
Почему Mojolicious?
db.dcp24.ru
От простых скриптовПочему Mojolicious?
#!/usr/bin/env perluse ojo;
g( 'http://bobina.pdj.ru/rss.xml' ) ->dom ->find( 'enclosure[url]' ) ->each(sub { say shift->attrs->{url} });
или TCP-клиента для сотни радио-потоков
Почему Mojolicious?
my $url = Mojo::URL->new( 'http://frod.io:8000/station20');$loop->connect( address => $url->host, port => $url->port, on_connect => sub { ... }, on_read => sub { ... },);$loop->start;
on_connect => sub { my ($self, $id) = @_; my $r = Mojo::Message::Request->new; $r->headers->header( 'Icy-MetaData' => 1 ); $r->url( $url ); $self->write($id, $r->to_string);},
on_read => sub { my ($self, $id, $chunk) = @_;
return unless my %tag = $chunk =~ /Stream(\w+)='(.*?)';/g; say $tag{Title};},
До софта по тиражированию фильмов для
цифровых кинотеатровПочему Mojolicious?
CopyDisk
Mojo::BaseMojo::Log
MojoX::RunCurses::Widgets
А также тесты для веб-сервисов
Почему Mojolicious?
use Test::More tests => 252;use Test::Mojo;
my $t = Test::Mojo->new(app => 'App'); # App.pmmy $url = '/api';# my $url = 'http://api.dev.frodio.com';
$t->get_ok( "$url/" ) ->status_is( 200 ) ->json_content_is({ hello => 'Hello, Frodio!' });
my $data = $t->post_form_ok("$url/like", {station_id => 2}, {'X-Frodio-Auth' => $auth}) ->status_is(200) ->tx->res->json;{ is ref $data, 'HASH'; is exists $data->{ok}, 1; is exists $data->{count}, 1; is defined $data->{sign}, 1, 'Like station';}
$t->post_form_ok("$url/logout/", {'X-Frodio-Auth' => $auth}) ->status_is(200);
Большинство своих задач я решаю,
используя Mojolicious
«Всегда хотел научиться делать
сайты»
etnogenez.ru
Небольшой сайт с полноценной панелью
управления26 модулей (140k), 91 шаблон (408k), 16 таблиц
Структура проектаВсе пути и запуски — от корня проекта
bin/conf/data/lib/log/script/t/tmpl/tmp/
bin/check.shbin/logs.shbin/mysqlbin/mysqldumpbin/restart.shbin/start.shbin/stop.sh
bin/start.sh
( # script/etnogenez daemon --reload starman --listen :3000 script/etnogenez) >> log/error.log &
conf/app.confconf/mysql.confconf/nginx.conf
conf/app.conf
{ secret => '*****', server => { www => $ENV{DEV} ? 'http://...' : 'http:/...', ... }, session => { ... }, log => { level => $ENV{DEV} ? 'debug' : 'warn', path => 'log/app.log', }, ...}
conf/mysql.conf
{ drivername => 'mysql', user => $ENV{DEV} ? 'dev' : 'не-dev', password => '******', datasource => { database => $ENV{DEV} ? 'dev' : 'не-dev', host => 'localhost', },};
data/log/tmp/
t/
script/
Стартовый скриптПути к библиотекам, настройка переменных
окружения
use common::sense;use lib qw(lib /tk/lib);
BEGIN { $ENV{DEV}++ if qx(pwd) =~ /dev/; $ENV{MOJO_MODE} ||= $ENV{DEV} ? 'dev' : 'production'; $ENV{MOJO_TMPDIR} = 'tmp/upload'; $ENV{MOJO_MAX_MESSAGE_SIZE} = 2 * 1024 ** 3;};$ENV{MOJO_APP} ||= 'App';
use Mojolicious::Commands;Mojolicious::Commands->start;
lib/
App.pm
package App;use Mojo::Base 'Mojolicious';
has conf => sub { do 'conf/app.conf' };has db => sub { use Util; Util->db( do 'conf/mysql.conf') };
sub startup { ... }
use DBI 1.58; use DBD::mysql 4.004; use DBI::Util;
return DBI->connect(DBI::Util::_parse_cfg( $conf, { RootClass => 'DBI::Util', mysql_enable_utf8 => 1, mysql_auto_reconnect => 1, }));
selectquery
inlimit
values
use dw;Lazy-обертка, связи parent/child и прочее
Контекстно проекту и БД
sub book { my $self = shift; SLICELY { $self->dw::g::part ('book_id') } 'id' => 'part' => ...}
sub part { my $self = shift; SLICELY { CHV {$_->[0]} $self->dw::g::book('id') } 'book_id' => 'book' => ...}
sub _list { my $self = shift; ... return $self->dw->book( $self->db->select( "select * from book where hidden=0 order by $order $limit" ) );}
bin/mysqlС базой данной работаю через консоль
Настройка путей, логов, сессий, типов
startup
Подключение плаггинов и хелперов
startup
$app->helper(db => sub { shift->app->db });
sub action { my $self = shift; # my $DB = $self->app->db;
$self->db->select('...');}
$app->helper(u => sub { my $self = shift; my $func = shift || return; return &{"Util::$func"}; });
$self->u(iso2human => '...');
%=u iso2human => '...'
# в каждом контроллереuse Util;
# в шаблоне или кодеUtil::iso2human(...);
# это boilercode и некрасиво# поэтому хелпер
Общие и контексно проекта
хелперыMojolicious::Plugin::UtilHelpers и App::Helpers
Все роутеры проектаstartup
route, bridge, waypoint, name, shortcut
Mojolicious::Guides::Routing
my $ad = $r->route('/admin')->to->name('admin');$ad->route('/login')->post->to('admin-enter#login'); my $a = $ad->bridge->to('admin-enter#check');
# shortcut / /sort /add /:id /:id/edit /:id/remove /:filter$a->crud($_ => "admin-$_") for qw(book part ...);
$a->route('/(*any)')->to('admin#not_found');
App::HelpersРазличные форматирования, работы со строками,
повторяющиеся действия
$app->helper(format_mmss => sub { my $self = shift; my $int = shift || return '00:00'; return sprintf "%02d:%02d", $int / 60, $int % 60; });
$app->helper(user_img => sub { my $self = shift; my $user = shift || $self->stash('USER'); return $user->{avatar} || '/.../default.png'; });
App::Index Контроллер
$r->route->to('index#main');
package App::Index;use App::Base -controller, with => ['App::News', 'App::Book', 'App::Audio'];
sub main { ... }
package App::Index;use Mojo::Base 'Mojolicious::Controller';use common::sense;
use App::News;use App::Book;use App::Audio;
has news => sub { App::News->new(%{ +shift }) };has book => sub { App::Book->new(%{ +shift }) };has audio => sub { App::Audio->new(%{ +shift }) };
package App::Index;use Mojo::Base 'Mojolicious::Controller';use common::sense;
use App::News;use App::Book;use App::Audio;
__PACKAGE__->attr(news => sub { App::News->new(%{ +shift }) });__PACKAGE__->attr(book => sub { App::Book->new(%{ +shift }) });__PACKAGE__->attr(audio => sub { App::Audio->new(%{ +shift }) };
Mojo::Base vs. App::Basecommon::sense, -controller, with
package App::Index;use App::Base -controller, with => ['App::News', 'App::Book', 'App::Audio'];
sub main { ... }
my $self = shift;my $limit = $self->conf('limit')->{index};
$self->render('index', news => $self->news->_last(limit => $limit->{news}), book => $self->book->_list, part => $self->audio->_last(limit => $limit->{part}),);
App::Book Контроллер
package App::Book;use App::Base -controller;
sub check { ... } # для bridge
sub list { ... }
sub item { ... }
sub _list { ... } # возращает данные
sub check { my $self = shift; return 0 unless my $book = $self->dw->book( $self->db->select( 'select * from book where name=? limit 1', $self->stash('book_name') ) )->[0]; $self->stash(book => $book); return 1;}
sub item { my $self = shift; my $book = $self->stash('book'); ...}
# роутеры
my $bn = $r->bridge('/book/:book_name')->to('book#check');
$bn->route->to('book#item')->name('book');
tmpl/
index.html.epШаблон
$r->route->to('index#main');
% layout 'default', title => '...';
<div id="column1">% for (@$news) { %== include 'news/item.inc', item => $_% }</div>
<span class="date"> %=u iso2humanM => $_->{published}</span>
<span class="download_count"> <%= format_digital($_->{listened}) %> раз</span>
$newsvs.
stash 'news'
layouts/default.html.eplayouts/default.mail.ep layouts/default.rss.eplayouts/admin.html.ep
etc/page.html.epetc/submenu.html.epadmin/etc/sort.txt.ep
Ни в коем случае сложной логики, тем более SQL :-)
exception.html.epexception.mail.ep
exception.dev.html.ep
% layout 'default', title => 'Страница временно недоступна', simple => 1;
<div class="error_page">Ошибка 500. Страница временно недоступна. Попробуйте позднее.</div>
% mail(to => conf('mail')->{devel}, template => 'exception', format => 'mail');
stash и defaultsСправочники и работа с ними
include 'etc/vars'Раньше был шаблон, который подключался везде
# app.confdefaults => { book_status => [ [soon => 'Готовится к изданию'], ..., [new => 'Новинки'], ], ...}
# App.pmif (my $d = $conf->{defaults}) { $self->defaults( $d ); for (keys %$d) { next unless ref $d->{$_} eq 'ARRAY'; $self->defaults($_ . '_hash' => { map { $_->[0] => $_->[1] } @{ $d->{$_} } }); }}
# в шаблоне
@$book_status
# или
$book_status_hash->{new}
Работы с формами
Я не использую никаких генераторов
форм
Формы для пользователей и модель данных — разные вещи
# App::Admin::Booksub add { my $self = shift; return $self->form unless $self->validate->book; # работа с полученными данными}
sub edit { my $self = shift; my $item = $self->stash('item'); # через bridge return $self->form unless $self->validate->book; ...}
admin/book/form.html.epШаблон может быть один
В итоге получаются очень простые контроллеры и шаблоны
И весь проект в целом
Вспомогательные скрипты
Рассылка по пользователям, графики для munin,cron-скрипты
# script/munin/user.pl
use MojoX::Loader;my $user = MojoX::Loader->load( controller => 'App::User');
say $user->_total;
# $user->db->select(...);# $user->conf('server')->{www}# $user->render_partial('...', stash1 => '..', stash2 => '..')# $user->mail(to => '..', template => '..')
MojoX::Loaderhttps://github.com/sharifulin/mojox-loader
Mojolicious очень удобный и простой инструмент
С большим количеством современных фитч
Хороший open source проект
Активное сообщество
И в принципе адекватый автор :-)
«Удивлен насколько легко читается код, даже для человека, который Perl видит второй раз в жизни»
Попробуйте Mojolicious прямо
сейчас!
Не будьте
use Mojolicious or die;
use Perl or die;
JFDI
Спасибо за внимание!Анатолий Шарифулин
YAPC::Russia 2011
Mojolicous by @vti