От переводчика: «Elixir и Phoenix — прекрасный пример того, куда движется современная веб-разработка. Уже сейчас эти инструменты предоставляют качественный доступ к технологиям реального времени для веб-приложений. Сайты с повышенной интерактивностью, многопользовательские браузерные игры, микросервисы — те направления, в которых данные технологии сослужат хорошую службу. Далее представлен перевод серии из 11 статей, подробно описывающих аспекты разработки на фреймворке Феникс казалось бы такой тривиальной вещи, как блоговый движок. Но не спешите кукситься, будет действительно интересно, особенно если статьи побудят вас обратить внимание на Эликсир либо стать его последователями.

В этой части мы добавим поддержку ролей и начнём разграничивать права доступа. Ключевой момент данной серии статей — здесь очень много внимания уделяется тестам, а это здорово!

Дочитайте до конца, чтобы узнать, зачем нужно подписываться на Wunsh.ru и как выиграть крайне полезный приз
».

На данный момент наше приложение основано на:

  • Elixir: v1.3.1
  • Phoenix: v1.2.0
  • Ecto: v2.0.2
  • Comeonin: v2.5.2

Где мы остановились


Мы расстались с вами в прошлый раз на том, что закончили связывать посты с пользователями и начали процесс по ограничению доступа к постам для пользователей, не являющихся их авторами. Нам также хотелось бы обеспечить соблюдение пользователями правил, не дожидаясь развития событий, при котором какой-нибудь негодник удалит всех пользователей, либо все посты.

Для решения этой проблемы мы воспользуемся довольно стандартным подходом: создадим роли.

Создание ролей


Начнём с запуска следующей команды в терминале:

$ mix phoenix.gen.model Role roles name:string admin:boolean

Которая должна вывести что-то похожее:

* creating web/models/role.ex
* creating test/models/role_test.exs
* creating priv/repo/migrations/20160721151158_create_role.exs

Remember to update your repository by running migrations:
$ mix ecto.migrate

Предлагаю воспользоваться советом скрипта и сразу же запустить команду mix ecto.migrate. Исходя из предположения, что наша база данных настроена должным образом, мы должны увидеть аналогичный вывод:

Compiling 21 files (.ex)

Generated pxblog app

11:12:04.736 [info]  == Running Pxblog.Repo.Migrations.CreateRole.change/0 forward
11:12:04.736 [info]  create table roles
11:12:04.742 [info]  == Migrated in 0.0s

Мы также прогоним тесты, чтобы убедиться, что добавление новой модели ничего не поломало. Если все они зелёные, то двинемся дальше и свяжем роли с пользователями.

Добавление связи между ролями и пользователями


Основной замысел, которому я следовал при реализации этой возможности — у каждого пользователя может быть только одна роль, при этом каждая роль принадлежит сразу нескольким пользователям. Для этого изменим файл web/models/user.ex в соответствии с написанным ниже.

Внутри секции схемы «users» добавим следующую строчку:

belongs_to :role, Pxblog.Role

В этом случае мы собираемся разместить внешний ключ role_id в таблице users, т. е. мы говорим, что пользователь «принадлежит» роли. Также откроем файл web/models/role.ex и добавим в секцию схемы «roles» следующую строчку:

has_many :users, Pxblog.User

Затем снова запустим тесты, но на этот раз получим кучу ошибок. Мы сказали Ecto, что наша таблица пользователей имеет связь с таблицей ролей, но нигде в базе данных не определили это. Так что нам нужно изменить таблицу пользователей, чтобы хранить ссылку на роль в поле role_id. Вызовем команду:

$ mix ecto.gen.migration add_role_id_to_users

Вывод:

Compiling 5 files (.ex)

* creating priv/repo/migrations
* creating priv/repo/migrations/20160721184919_add_role_id_to_users.exs

Давайте откроем свежесозданный файл миграции. По умолчанию он выглядит так:


defmodule Pxblog.Repo.Migrations.AddRoleIdToUsers do
  use Ecto.Migration
  def change do
  end
end

Нам нужно внести несколько коррективов. Начнём с изменения таблицы users. Добавим в неё ссылку на роли таким образом:


alter table(:users) do
  add :role_id, references(:roles)
end

Нам также нужно добавить индекс на поле role_id:

create index(:users, [:role_id])

Наконец, выполним команду mix ecto.migrate снова. Миграция должна пройти успешно! Если мы запустим тесты теперь, все они снова будут зелёными!

К сожалению, наши тесты не идеальны. Прежде всего мы не изменяли их вместе с моделями Post/User. Поэтому не можем быть уверенными в том, что, например, у поста обязательно определён пользователь. Аналогично, у нас не должно быть возможности создавать пользователей без роли. Изменим функцию changeset в файле web/models/user.ex следующим образом (обратите внимание на добавление :role_id в двух местах):


def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:username, :email, :password, :password_confirmation, :role_id])
    |> validate_required([:username, :email, :password, :password_confirmation, :role_id])
    |> hash_password
end

Создание хелпера для тестов


В результате запуска тестов сейчас можно получить большое количество ошибок, но это нормально! Нам нужно проделать много работы, чтобы привести их в порядок. И начнём мы с добавления некого тестового хелпера, который избавит нас от написания одного и того же кода снова и снова. Создадим новый файл test/support/test_helper.ex и заполним его следующим кодом:


defmodule Pxblog.TestHelper do
  alias Pxblog.Repo
  alias Pxblog.User
  alias Pxblog.Role
  alias Pxblog.Post
  
  import Ecto, only: [build_assoc: 2]
  
  def create_role(%{name: name, admin: admin}) do
    Role.changeset(%Role{}, %{name: name, admin: admin})
    |> Repo.insert
  end
  
  def create_user(role, %{email: email, username: username, password: password, password_confirmation: password_confirmation}) do
    role
    |> build_assoc(:users)
    |> User.changeset(%{email: email, username: username, password: password, password_confirmation: password_confirmation})
    |> Repo.insert
  end
  
  def create_post(user, %{title: title, body: body}) do
    user
    |> build_assoc(:posts)
    |> Post.changeset(%{title: title, body: body})
    |> Repo.insert
  end
end

Перед тем, как двинемся править тесты дальше, давайте поговорим о том, что же этот файл делает. Первое, на что следует обратить внимание — это то, куда мы его положили. А именно в директорию test/support, в которую мы так же можем класть любые модули, чтобы сделать их доступными нашим тестам в целом. Нам по-прежнему нужно будет ссылаться на этот хелпер из каждого тестового файла, но так и должно быть!

Итак, сначала мы указываем алиасы для модулей Repo, User, Role и Post, чтобы укоротить синтаксис для их вызова. Затем импортируем Ecto, чтобы получить доступ к функции build_assoc для создания ассоциаций.

В функции create_role мы ожидаем получить на вход словарь, включающий название роли и флаг администратора. Так как мы воспользовались здесь функцией Repo.insert, значит получим на выходе стандартный ответ {:ok, model} при успешном добавлении. Другими словами это просто вставка ревизии Role.

Мы начинаем двигаться по цепочке вниз с полученной на вход роли, которую передаём дальше для создания модели пользователя (т.к. мы определили :users в качестве ассоциации), на основе которой создаём ревизию User с упомянутыми ранее параметрами. Конечный результат передаём в функцию Repo.insert() и всё готово!

Хоть и будучи сложным для объяснения, мы имеем дело с супер читаемым и супер понятным кодом. Получаем роль, создаём связанного с ней пользователя, подготавливаем его для добавления в базу данных и затем непосредственно добавляем!

В функции create_post мы делаем аналогичные вещи, за исключением того, что вместо пользователя и роли мы работаем с постом и пользователем!

Исправляем тесты


Начнём с правки файла test/models/user_test.exs. Сперва нам нужно добавить alias Pxblog.TestHelper в самый верх определения модуля, что позволит использовать удобные хелперы, созданные нами чуть раньше. Затем мы создадим блок setup перед тестами, чтобы повторно использовать роль.


setup do
  {:ok, role}  = TestHelper.create_role(%{name: "user", admin: false})
  {:ok, role: role}
end

А затем в первом же тесте с помощью сопоставления с образцом, мы получим роль из блока setup. Давайте сэкономим себе ещё немного времени и напишем функцию-хелпер для получения валидных атрибутов вместе с ролью:


defp valid_attrs(role) do
  Map.put(@valid_attrs, :role_id, role.id)
end

test "changeset with valid attributes", %{role: role} do
  changeset = User.changeset(%User{}, valid_attrs(role))
  assert changeset.valid?
end

Подведём итог. Мы сопоставляем с образцом ключ role, получаемый из блока setup, а затем изменяем ключ valid_attrs, чтобы включить валидную роль в наш хелпер! Как только мы изменим этот тест и запустим его снова, то сразу же вернёмся к зелёному состоянию файла test/models/user_test.exs.

Теперь откройте файл test/controllers/user_controller_test.exs. Для прохождения тестов из него мы воспользуемся теми же самыми уроками. В самый верх добавим инструкцию alias Pxblog.Role, а также alias Pxblog.TestHelper следом. После чего расположим блок setup, в котором создаётся роль и возвращается объект conn:

setup do
  {:ok, user_role}  = TestHelper.create_role(%{name: "user", admin: false})
  {:ok, admin_role} = TestHelper.create_role(%{name: "admin", admin: true})
  {:ok, conn: build_conn(), user_role: user_role, admin_role: admin_role}
end

Добавим хелпер valid_create_attrs, принимающий роль в качестве аргумента, и возвращающий новый словарь валидных атрибутов с добавленным role_id.


defp valid_create_attrs(role) do
  Map.put(@valid_create_attrs, :role_id, role.id)
end

Наконец сделаем так, чтобы действия create и update использовали этот хелпер, а также сопоставление с образцом значения user_role из нашего словаря.


test "creates resource and redirects when data is valid", %{conn: conn, user_role: user_role} do
  conn = post conn, user_path(conn, :create), user: valid_create_attrs(user_role)
  assert redirected_to(conn) == user_path(conn, :index)
  assert Repo.get_by(User, @valid_attrs)
end

test "updates chosen resource and redirects when data is valid", %{conn: conn, user_role: user_role} do
  user = Repo.insert! %User{}
  conn = put conn, user_path(conn, :update, user), user: valid_create_attrs(user_role)
  assert redirected_to(conn) == user_path(conn, :show, user)
  assert Repo.get_by(User, @valid_attrs)
end

Теперь все тесты контроллера пользователей должны проходить! Тем не менее, запуск mix test по-прежнему показывает ошибки.

Исправляем тесты контроллера постов


Работу с тестами PostController мы завершили на добавлении кучи хелперов, облегчающих создание постов с пользователями. Поэтому теперь нам нужно добавить в них концепцию ролей, чтобы можно было создавать валидных пользователей. Начнём с добавления ссылки на Pxblog.Role в самый верх файла test/controllers/post_controller_test.exs:


alias Pxblog.Role
alias Pxblog.TestHelper

Затем создадим блок setup, немного отличающийся от того, что мы делали ранее.


setup do
  {:ok, role} = TestHelper.create_role(%{name: "User Role", admin: false})
  {:ok, user} = TestHelper.create_user(role, %{email: "test@test.com", username: "testuser", password: "test", password_confirmation: "test"})
  {:ok, post} = TestHelper.create_post(user, %{title: "Test Post", body: "Test Body"})
  conn = build_conn() |> login_user(user)
  {:ok, conn: conn, user: user, role: role, post: post}
end

Первое, что мы здесь сделали — создали стандартную роль без администраторских привелегий. На следующей строчке на основе этой роли мы создали пользователя. Затем для этого пользователя создали пост. Мы уже обсуждали кусок со входом, так что идём дальше. Наконец, мы возвращаем все только что созданные модели, чтобы каждый из тестов с помощью сопоставления с образцом выбрал из них те, что ему нужны.

Нам требуется изменить всего один тест, чтобы всё снова стало зелёным. Тест “redirects when trying to edit a post for a different user” падает потому, что пытается создать на лету ещё одного пользователя, ничего не зная о роли. Чуть-чуть поправим его:


test "redirects when trying to edit a post for a different user", %{conn: conn, user: user, role: role, post: post} do
  {:ok, other_user} = TestHelper.create_user(role, %{email: "test2@test.com", username: "test2", password: "test", password_confirmation: "test"})
  conn = get conn, user_post_path(conn, :edit, other_user, post)
  assert get_flash(conn, :error) == "You are not authorized to modify that post!"
  assert redirected_to(conn) == page_path(conn, :index)
  assert conn.halted
end

Итак, мы добавили получение роли через сопоставление с образцом в определении теста, а затем немного изменили создание other_user, чтобы и здесь использовался TestHelper вместе с полученной ролью.

У нас появилась возможность для рефакторинга благодаря тому, что мы добавили объект post из TestHelper в качестве одного из значений, которые можно получить с помощью сопоставления с образцом. Поэтому мы можем изменить все вызовы build_post на объект post, полученный таким образом. После всех изменений файл должен выглядеть следующим образом:


defmodule Pxblog.PostControllerTest do
  use Pxblog.ConnCase

  alias Pxblog.Post
  alias Pxblog.TestHelper

  @valid_attrs %{body: "some content", title: "some content"}
  @invalid_attrs %{}

  setup do
    {:ok, role} = TestHelper.create_role(%{name: "User Role", admin: false})
    {:ok, user} = TestHelper.create_user(role, %{email: "test@test.com", username: "testuser", password: "test", password_confirmation: "test"})
    {:ok, post} = TestHelper.create_post(user, %{title: "Test Post", body: "Test Body"})
    conn = build_conn() |> login_user(user)
    {:ok, conn: conn, user: user, role: role, post: post}
  end

  defp login_user(conn, user) do
    post conn, session_path(conn, :create), user: %{username: user.username, password: user.password}
  end

  test "lists all entries on index", %{conn: conn, user: user} do
    conn = get conn, user_post_path(conn, :index, user)
    assert html_response(conn, 200) =~ "Listing posts"
  end

  test "renders form for new resources", %{conn: conn, user: user} do
    conn = get conn, user_post_path(conn, :new, user)
    assert html_response(conn, 200) =~ "New post"
  end

  test "creates resource and redirects when data is valid", %{conn: conn, user: user} do
    conn = post conn, user_post_path(conn, :create, user), post: @valid_attrs
    assert redirected_to(conn) == user_post_path(conn, :index, user)
    assert Repo.get_by(assoc(user, :posts), @valid_attrs)
  end

  test "does not create resource and renders errors when data is invalid", %{conn: conn, user: user} do
    conn = post conn, user_post_path(conn, :create, user), post: @invalid_attrs
    assert html_response(conn, 200) =~ "New post"
  end

  test "shows chosen resource", %{conn: conn, user: user, post: post} do
    conn = get conn, user_post_path(conn, :show, user, post)
    assert html_response(conn, 200) =~ "Show post"
  end

  test "renders page not found when id is nonexistent", %{conn: conn, user: user} do
    assert_error_sent 404, fn ->
      get conn, user_post_path(conn, :show, user, -1)
    end
  end

  test "renders form for editing chosen resource", %{conn: conn, user: user, post: post} do
    conn = get conn, user_post_path(conn, :edit, user, post)
    assert html_response(conn, 200) =~ "Edit post"
  end

  test "updates chosen resource and redirects when data is valid", %{conn: conn, user: user, post: post} do
    conn = put conn, user_post_path(conn, :update, user, post), post: @valid_attrs
    assert redirected_to(conn) == user_post_path(conn, :show, user, post)
    assert Repo.get_by(Post, @valid_attrs)
  end

  test "does not update chosen resource and renders errors when data is invalid", %{conn: conn, user: user, post: post} do
    conn = put conn, user_post_path(conn, :update, user, post), post: %{"body" => nil}
    assert html_response(conn, 200) =~ "Edit post"
  end

  test "deletes chosen resource", %{conn: conn, user: user, post: post} do
    conn = delete conn, user_post_path(conn, :delete, user, post)
    assert redirected_to(conn) == user_post_path(conn, :index, user)
    refute Repo.get(Post, post.id)
  end

  test "redirects when the specified user does not exist", %{conn: conn} do
    conn = get conn, user_post_path(conn, :index, -1)
    assert get_flash(conn, :error) == "Invalid user!"
    assert redirected_to(conn) == page_path(conn, :index)
    assert conn.halted
  end

  test "redirects when trying to edit a post for a different user", %{conn: conn, role: role, post: post} do
    {:ok, other_user} = TestHelper.create_user(role, %{email: "test2@test.com", username: "test2", password: "test", password_confirmation: "test"})
    conn = get conn, user_post_path(conn, :edit, other_user, post)
    assert get_flash(conn, :error) == "You are not authorized to modify that post!"
    assert redirected_to(conn) == page_path(conn, :index)
    assert conn.halted
  end
end

Исравляем тесты для Session Controller


Некоторые тесты из файла test/controllers/session_controller_test.exs не проходят из-за того, что мы не сказали им начать использовать наш TestHelper. Как и раньше добавим алиасы наверх файла и изменим блок setup:


defmodule Pxblog.SessionControllerTest do
  use Pxblog.ConnCase

  alias Pxblog.User
  alias Pxblog.TestHelper

  setup do
    {:ok, role} = TestHelper.create_role(%{name: "user", admin: false})
    {:ok, _user} = TestHelper.create_user(role, %{username: "test", password: "test", password_confirmation: "test", email: "test@test.com"})
    {:ok, conn: build_conn()}
  end

Этого должно быть достаточно, чтобы тесты начали проходить! Ура!

Исправляем оставшиеся тесты


У нас по-прежнему есть два сломанных теста. Так сделаем же их зелёными!

1) test current user returns the user in the session (Pxblog.LayoutViewTest)
 test/views/layout_view_test.exs:13
 Expected truthy, got nil
 code: LayoutView.current_user(conn)
 stacktrace:
 test/views/layout_view_test.exs:15

2) test current user returns nothing if there is no user in the session (Pxblog.LayoutViewTest)
 test/views/layout_view_test.exs:18
 ** (ArgumentError) cannot convert nil to param
 stacktrace:
 (phoenix) lib/phoenix/param.ex:67: Phoenix.Param.Atom.to_param/1
 (pxblog) web/router.ex:1: Pxblog.Router.Helpers.session_path/4
 test/views/layout_view_test.exs:20

В самом верху файла test/views/layout_view_test.exs можно увидеть как создаётся пользователь без роли! В блоке setup мы также не возвращаем этого пользователя, что и приводит к таким печальным последствиям! Ужас! Так что давайте скорее отрефакторим весь файл:


defmodule Pxblog.LayoutViewTest do
  use Pxblog.ConnCase, async: true

  alias Pxblog.LayoutView
  alias Pxblog.TestHelper

  setup do
    {:ok, role} = TestHelper.create_role(%{name: "User Role", admin: false})
    {:ok, user} = TestHelper.create_user(role, %{email: "test@test.com", username: "testuser", password: "test", password_confirmation: "test"})
    {:ok, conn: build_conn(), user: user}
  end

  test "current user returns the user in the session", %{conn: conn, user: user} do
    conn = post conn, session_path(conn, :create), user: %{username: user.username, password: user.password}
    assert LayoutView.current_user(conn)
  end

  test "current user returns nothing if there is no user in the session", %{conn: conn, user: user} do
    conn = delete conn, session_path(conn, :delete, user)
    refute LayoutView.current_user(conn)
  end
end

Здесь мы добавляем алиас для модели Role, создаём валидную роль, создаём валидного пользователя с этой ролью и затем возвращаем получившегося пользователя с объектом conn. И в конце концов в обеих тестовых функциях мы получаем пользователя с помощью сопоставления с образцом. Теперь запускаем mix test и…

Все тесты зелёные! Но мы получили несколько предупреждений (т.к. слишком перестарались с заботой о чистом коде).

test/controllers/post_controller_test.exs:20: warning: function create_user/0 is unused
test/views/layout_view_test.exs:6: warning: unused alias Role
test/views/layout_view_test.exs:5: warning: unused alias User
test/controllers/user_controller_test.exs:5: warning: unused alias Role
test/controllers/post_controller_test.exs:102: warning: variable user is unused
test/controllers/post_controller_test.exs:6: warning: unused alias Role

Для исправления просто зайдите в каждый из этих файлов и удалите проблемные алиасы и функции, т.к. они нам больше не нужны!

$ mix test

Вывод:

.........................................
Finished in 0.4 seconds
41 tests, 0 failures
Randomized with seed 588307

Создаём начальные данные администратора


В конечном счёте нам нужно позволить создавать новых пользователей только администратору. Но тем самым это означает, что возникнет ситуация, при которой изначально мы не сможем создать ни пользователей, ни администраторов. Вылечим этот недуг с помощью добавления начальных данных (seed data) для администратора по умолчанию. Для этого откроем файл priv/repo/seeds.exs и вставим в него следующий код:


alias Pxblog.Repo
alias Pxblog.Role
alias Pxblog.User

role = %Role{}
  |> Role.changeset(%{name: "Admin Role", admin: true})
  |> Repo.insert!
admin = %User{}
  |> User.changeset(%{username: "admin", email: "admin@test.com", password: "test", password_confirmation: "test", role_id: role.id})
  |> Repo.insert!

И затем загрузим наши сиды с помощью выполнения следующей команды:

$ mix run priv/repo/seeds.exs

Что будет дальше


Теперь, когда наши модели настроены и готовы взаимодействовать с ролям, а все тесты снова зелёные, нам нужно начать добавлять контроллерам функционал для ограничения определённых операций, если у пользователя нет прав для их исполнения. В следующем посте мы рассмотрим как лучше всего реализовать этот функционал, как добавить модуль-хелпер, и конечно же, как удержать тесты зелёными.

Заключение от Вуншей


У нас сразу две отличные новости! Во-первых, нас становится больше, и благодаря этому все подписчики уже завтра получат на почту новую статью (которая не относится к данному циклу) о том, чем Эликсир притягивает разработчиков и благодаря чему их удерживает. Так что, если вы ещё не подписались, то не теряйте времени и скорее делайте это!

Во-вторых, мы решили разыграть супер-полезный подарок — книгу Dave Thomas «Programming Elixir». Великолепное введение в язык (и далеко не только для новичков) от гуру обучающей литературы по программированию Дэйва Томаса. Для того, чтобы заполучить её, вам необходимо опубликовать пост на Хабрахабре на тему языка Elixir и указать, что статья опубликована специально для конкурса от Wunsh.ru. Победителем станет человек, чья статья наберёт наибольший рейтинг. Подробные условия читайте по ссылке.

Другие части:
  1. Вступление
  2. Авторизация
  3. Добавление ролей
  4. Скоро...

Успехов в изучении, оставайтесь с нами!
Поделиться с друзьями
-->

Комментарии (31)


  1. Fedcomp
    15.11.2016 10:10

    > Дочитайте до конца, чтобы узнать, зачем нужно подписываться на Wunsh.ru и как выиграть крайне полезный приз».
    Серьезно?
    > Ruby on Rails*, Ruby*,
    Причем тут Ruby/RoR?


    1. jarosluv
      15.11.2016 10:14

      Спасибо, что являетесь постоянным комментатором наших переводов!

      Таким бесхитростным образом мы аккуратно пытаемся подначить вас добавить в свою и без того весёлую Рельсовую жизнь немножечко Феникса.


      1. Source
        15.11.2016 15:13

        Хоть я Вас не минусую, но соглашусь с Fedcomp… Это тупо неуважение к Ruby-сообществу и печально, что Вы при этом прикрываетесь Elixir-сообществом, разжигая неприятие.


        1. jarosluv
          15.11.2016 16:14
          -1

          Я, как автор этого перевода, так не считаю. Язык Elixir состоит из частей Ruby и Erlang, следовательно косвенно относится к ним обоим. Хабы помогают поделиться материалом с потенциально заинтересованными этой темой людьми, и пока нет никакой другой возможности расширить аудиторию, я буду пользоваться этой.

          Ну а о том, что бесплатные переводы по схожей Ruby тематике разжигают какую-либо ненависть, я вообще отказываюсь комментировать.


          1. Fedcomp
            15.11.2016 16:27

            Из каких частей Ruby состоит Elixir? там есть ООП?


            1. jarosluv
              15.11.2016 16:36

              Из автора-рубиста.


              1. Fedcomp
                15.11.2016 16:50

                Что то я не вижу чтобы кто то пихал хаб C в Ruby/RoR


                1. jarosluv
                  15.11.2016 16:54

                  А зачем, когда есть наполненный подписчиками хаб Ruby/RoR?


                  1. Fedcomp
                    18.11.2016 23:28

                    Вы еще в сисадминистрирование добавьте, он ведь набит подписчиками.


            1. Desprit
              15.11.2016 17:27
              +1

              Из таких, что любая литература по Elixir+Phoenix совершенно прямо заявляет, что как язык, так и его фреймворк, оба наследуют идеи Ruby+RoR.


              1. Source
                15.11.2016 17:43

                Во-первых, далеко не любая… Во-вторых, это хоть и популярное, но заблуждение. По-факту, что языки, что фреймворки придерживаются совершенно разных идеологий. И любые ассоциации на тему похожести скорее навредят Вам при изучении Elixir и Phoenix, чем помогут. Хотите ассоциаций — сравнивайте с Erlang и CommonLisp.


                1. Desprit
                  15.11.2016 18:54
                  +1

                  Лично мне не навредят ни сравнения с Ruby/RoR, ни с Erlang, потому что я из другого бэкграунда пришел к эликсиру. По сути, для меня это просто слова, которые, повторюсь, заявляются отовсюду, в т.ч. из основных книг, которые рекомендует community — «Programming Elixir» и «Programming Phoenix», а также из всевозможных гайдов и даже с офф сайта.
                  Я ваше неодобрение хабами понимаю. Но по мне так Phoenix имеет отношение к Erlang примерно настолько же отдаленное, как Elixir — к Ruby. Поэтому запихнуть топик про фреймворк только лишь в хаб эрланга — это тоже неверно.


                  1. Source
                    15.11.2016 21:54

                    Хорошо, что лично Вам не помешает.
                    С Erlang/OTP всё-таки есть прямая связь: Elixir код выполняется на виртуальной машине Erlang, половина его stdlib — обертки над stdlib Erlang, любое приложение на Elixir использует OTP, а Phoenix так вообще очень плотно на OTP опирается.
                    Что касается Ruby, то связь только такая: на обоих языках приятно программировать и автор Elixir имеет богатый опыт программирования на Ruby. Всё, на этом полезные параллели между Elixir и Ruby заканчиваются.


                    1. NLO
                      16.11.2016 00:09

                      НЛО прилетело и опубликовало эту надпись здесь


                      1. Source
                        16.11.2016 00:50

                        Разве что что-то концептуальное, типа единая утилита для управления всем — mix вместо bundler и rake. И plug напоминает rack.
                        Но в плане деталей и реализации языки совершенно разные. А фреймворки скорее вообще противоположные по идеологии.


          1. Source
            15.11.2016 17:30

            Как автор перевода Вы вольны делать что хотите. Но если прикрываетесь сообществом, то не позорьте его.


            Ну а о том, что бесплатные переводы по схожей Ruby тематике разжигают какую-либо ненависть, я вообще отказываюсь комментировать.

            Не ненависть, а неприятие… Или Вам лично нравится получать спам? Те, кому интересен Elixir подпишутся на Erlang/OTP (пока нет более подходящего хаба)


            1. jarosluv
              15.11.2016 18:26

              Никого и ничего не позорю. Даже не оскорбляю ничьих чувств. Возможно, кроме ваших. Но я правда не хочу этого делать. Я лишь стараюсь всеми силами развивать тему Эликсира и никакого тайного умысла спамить в вашу ленту не имею, также не преследую целей разжигать что-либо кроме страстной любви к языку программирования Эликсир.

              И я очень рад, что вы выражаете своё мнение в комментариях, рассуждаете на тему подходящих хабов и стоите горой за оскорблённых рубистов (т.е. страдаете в том числе и за меня). Может быть мне даже удастся почерпнуть что-либо полезное из всего этого!


              1. Source
                15.11.2016 22:06

                Лично мою ленту Вы не спамите, т.к. я на все 5 хабов подписан. Но я понимаю недовольство, которое Вам в комментариях к каждой статье высказывают по поводу неуместности хабов Ruby/RoR. На основании 2 лет программирования на Elixir, я могу сказать, что строить параллели с Ruby — это самое вредное при изучении Elixir. В этом плане Вы и себе палки в колеса вставляете и всем, кто поверит в то, что эти языки похожи.
                P.S. Если Вам так хочется хаб Ruby к статье, то можно Elixir is not Ruby перевести, он там хоть упоминается ;-)


                1. jarosluv
                  15.11.2016 22:27

                  К каждой статье высказывает недовольство один и тот же человек, а ещё один человек безустанно его поддерживает. :)

                  Я прекрасно понимаю о чём вы говорите и разрабатываю обучающие материалы как раз в этом направлении, что Elixir заряжен духом Ruby, но это не Ruby.

                  За статью отдельное спасибо!


                  1. Source
                    15.11.2016 23:29
                    +1

                    Хех, ну хоть какое-то обсуждение… может Вы для этого лишние хабы и добавляете :-)


                  1. uniqm
                    16.11.2016 10:24

                    Из статистики: на одного публично недовольного Вашим продуктом есть девять таких же недовольных, но не высказавших Вам это открыто.
                    Я из тех девяти =) Ну и вправду, зачем там ruby/RoR? Elixir сам по себе уже достаточно на слуху, что бы не было необходимости тащить его на волне популярности ruby/RoR.

                    Как по мне, то Elixir вполне себе ничего, если близка машина эрланга. Да, время ответа сильно ниже, чем на рельсах. Но математика как и у эрланга — не на высоте.
                    В этом плане более пристально имеет смысле следить за Crystal. Даже Матц на недавнем выступлении о нем начал вещать, https://youtu.be/2KL3wNXl16g?t=7011


                    1. imgen
                      16.11.2016 10:48

                      Можно подробнее о том, что в Erlang не так с математикой?


                      1. uniqm
                        16.11.2016 12:24

                        Ну она медленнее, чем у языков из разряда: Go, Crystal, D, Rust, Nim и тд.
                        Холиварно все это, не могу себе позволить продолжать ;)


                        1. Desprit
                          17.11.2016 08:12

                          То есть, начать вы себе позволили, а продолжать — это уже холиварно? :)


                          1. uniqm
                            17.11.2016 12:13
                            +1

                            Подловил ;) Да, некрасиво вышло…
                            Ну к примеру задачка: принимаем изображения от юзеров и желаем делать разные превьюшки, плюс добавляем фильтры/обработки как-нибудь хитро. И вот теперь выбор инструментов для решения: нам нужна очередь и демоны для нашей магии. Где используем эрланг, а где тот же С? =) RabbitMQ для очереди, ибо эрланг хорош в этой части, а С для демонов по магии с картинками.


  1. Desprit
    15.11.2016 13:07
    +1

    Спасибо за труд! Жду следующих частей :)


    1. jarosluv
      15.11.2016 13:12

      Спасибо за положительный отзыв! Подобная реакция заставляет верить, что это делается не зря, особенно когда некоторые уникумы из раза в раз минусуют не статью, а сразу карму. :)


      1. Desprit
        16.11.2016 13:41

        А вы не могли бы сделать в конце или начале что-то вроде оглавления со ссылками на другие части?


        1. jarosluv
          16.11.2016 20:10

          Спасибо за предложение, сделаю!)


        1. jarosluv
          16.11.2016 20:18

          Готово!


          1. Desprit
            16.11.2016 21:10

            Спасибо!