1

Тема: Пишемо міні-блог на elixir-phoenix [Стаття]

Написання міні-блогу на elixir-phoenix

"Встановлення всякого-різного перед власне написанням коду"

1. erlang
   Встановлюємо звідси - https://www.erlang-solutions.com/resour … nload.html
   я встановлював "Installation using repository" (debian), а для вінди - просто завантажував інсталяшку

2. elixir
   http://elixir-lang.org/install.html

3. phoenix
   http://www.phoenixframework.org/docs/installation
   
   3.1. Hex
     

$ mix local.hex

   
   3.2. node.js (>= 5.0.0))
      https://nodejs.org/download/release/
      
   3.3. phoenix archive
     

$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez

4. PostgreSQL
   https://www.postgresql.org/download/linux/debian/

на момент написання статті у мене - Erlang/OTP 19, Elixir 1.3.1, phoenix 1.2.0, node.js v6.3.0
(elixir -v, mix deps, node -v)

1. створення проекту
   

$ cd /home/elixir
   $ mix phoenix.new blog

   висвітиться повідомлення про створення файлів проекту
   (по замовчуванню проект створюється у home, користувач у мене elixir)

2. нам потрібно прописати налаштування до бд, порти, сертифікати(при потребі)...
 

$ cd blog
  $ vi /home/elixir/blog/config/dev.exs
  

 
  після 9ї строчки (config :blog, Blog.Endpoint,)
  ми дописуємо
 

  http: [port: 4000],
  https: [port: 4443,
          otp_app: :hello_phoenix,
          keyfile: "/etc/letsencrypt/live/www.test.club/privkey.pem",
          certfile: "/etc/letsencrypt/live/www.test.club/fullchain.pem"],
  

 
  внизу файла, після строк (config :blog, Blog.Repo,
  adapter: Ecto.Adapters.Postgres,)
  дописуємо
 

username: "elixir",
  password: "7BHp5ssDV181Iegd5Dj0YZmFlvWUgTnl",
  database: "blog",
  hostname: "localhost",
  pool_size: 10,
  port: 5432

 
  получиться у нас отаке -- https://github.com/221V/mini-blog/blob/ … ig/dev.exs
 
  після цього нам потрібно згенерувати-зкомпілювати файли підключення до бд та підключитись
 

$ mix ecto.create

  висвітиться повідомлення про генерацію-компіляцію файлів
 
  (детальніше про Ecto - https://hexdocs.pm/ecto/Ecto.html )
 
3. запускаємо проект і йдемо дивитись що у нас є
   

$ mix phoenix.server

   у браузері бачимо щось таке --
   http://f5.s.сайт-злодій/15iw6PV39.png
   Вітаю!

2

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

Йдемо далі -
1. генерування CRUD додатка
   у phoenix є генератори - http://www.phoenixframework.org/docs/mix-tasks
   одним із них ми зараз і скористуємося:
   

$ mix phoenix.gen.html Post posts title:string body:text

   висвітиться повідомлення про створення файлів

2. routing, controller та таблиці бд
   відредагуємо web/router.ex ---
   додамо строку
   

resources "/posts", PostController

   після строки
   

get "/", PageController, :index

   
   вивести шляхи у консоль можна за допомогою
   

$ mix phoenix.routes

   
   створимо бд проекту та застосуємо до неї зміни (вище ми використовували генератор - він також створив таблицю)
   

$ mix ecto.create
   $ mix ecto.migrate

   
3. додамо коментарі
   для цього скористуємося генератором -
   

$ mix phoenix.gen.model Comment comments name:string content:text post_id:references:posts

   
   додамо зовнішній ключ у моделі Comment
   

"результат"

   

   defmodule Blog.Comment do
  use Blog.Web, :model

  schema "comments" do
    field :name, :string
    field :content, :string
    belongs_to :post, Blog.Post

    timestamps()
  end

  @required_fields ~w(name content post_id)
  @optional_fields ~w()
  
  @doc """
  Creates a changeset based on the `model` and `params`.
  
  If `params` are nil, an invalid changeset is returned
  with no validation performed.
  """
  def changeset(model, params \\ %{}) do
    model
    |> cast(params, @required_fields, @optional_fields)
  end
end
   

   

   
   також пропишемо у моделі Post звязок один до багатьох (один пост - коментарів багато), звязок для видалення, функцію підрахунку кількості коментарів
   

"результат"

   

   defmodule Blog.Post do
  use Blog.Web, :model

  schema "posts" do
    field :title, :string
    field :body, :string

    has_many :comments, Blog.Comment, on_delete: :delete_all
    
    timestamps()
  end
  
  @required_fields ~w(title body)
  @optional_fields ~w()

  @doc """
  Creates a changeset based on the `model` and `params`.
  
  If `params` are nil, an invalid changeset is returned
  with no validation performed.
  """
  def changeset(model, params \\ %{}) do
    model
    |> cast(params, @required_fields, @optional_fields)
  end
  
  def count_comments(query) do
    from p in query,
      group_by: p.id,
      left_join: c in assoc(p, :comments),
      select: {p, count(c.id)}
  end
  
end
   

   

   
   не забуваємо про
   

$ mix ecto.migrate

   
   зараз у нас є коментарі у базі,
   тепер потрібно додати коментарі в routing - router.ex
   

"результат"

   

   defmodule Blog.Comment do
  use Blog.Web, :model

  schema "comments" do
    field :name, :string
    field :content, :string
    belongs_to :post, Blog.Post

    timestamps()
  end

  @required_fields ~w(name content post_id)
  @optional_fields ~w()
  
  @doc """
  Creates a changeset based on the `model` and `params`.
  
  If `params` are nil, an invalid changeset is returned
  with no validation performed.
  """
  def changeset(model, params \\ %{}) do
    model
    |> cast(params, @required_fields, @optional_fields)
  end
end
   

   

   
   також пропишемо у моделі Post звязок один до багатьох (один пост - коментарів багато), а також звязок для видалення
   

"результат"

   

   defmodule Blog.Router do
  use Blog.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", Blog do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    resources "/posts", PostController do
      post "/comment", PostController, :add_comment
    end
    
  end

  # Other scopes may use custom stacks.
  # scope "/api", Blog do
  #   pipe_through :api
  # end
end
   

   

   
   подивимось зміни -
   

$ mix phoenix.routes
   
   post_post_path  POST    /posts/:post_id/comment  Blog.PostController :add_comment

   
   змінимо Post контроллер -
   додамо аліас (детальніше про аліаси - http://elixir-lang.org/getting-started/ … mport.html ),
   додамо scrub_params (детальніше про scrub_params - https://hexdocs.pm/phoenix/Phoenix.Cont … b_params/2 )
   додамо функції add_comment, show
   

"результат"

   

   defmodule Blog.PostController do
  use Blog.Web, :controller

  alias Blog.Post
  alias Blog.Comment
  
  plug :scrub_params, "post" when action in [:create, :update]
  plug :scrub_params, "comment" when action in [:add_comment]

  def index(conn, _params) do
    posts = Post
    |> Post.count_comments
    |> Repo.all
    render(conn, "index.html", posts: posts)
  end

  def new(conn, _params) do
    changeset = Post.changeset(%Post{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"post" => post_params}) do
    changeset = Post.changeset(%Post{}, post_params)

    if changeset.valid? do
        Repo.insert(changeset)
        
        conn
        |> put_flash(:info, "Post created successfully.")
        |> redirect(to: post_path(conn, :index))
    else
        render(conn, "new.html", changeset: changeset)
    end
  end
  
  def show(conn, %{"id" => id}) do
    post = Repo.get(Post, id) |> Repo.preload([:comments])
    changeset = Comment.changeset(%Comment{})
    render(conn, "show.html", post: post, changeset: changeset)
  end

  def edit(conn, %{"id" => id}) do
    post = Repo.get!(Post, id)
    changeset = Post.changeset(post)
    render(conn, "edit.html", post: post, changeset: changeset)
  end

  def update(conn, %{"id" => id, "post" => post_params}) do
    post = Repo.get!(Post, id)
    changeset = Post.changeset(post, post_params)

    if changeset.valid? do
        Repo.update(changeset)
        
        conn
        |> put_flash(:info, "Post updated successfully.")
        |> redirect(to: post_path(conn, :index))
    else
        render(conn, "edit.html", post: post, changeset: changeset)
    end
  end

  def delete(conn, %{"id" => id}) do
    post = Repo.get!(Post, id)
    # Here we use delete! (with a bang) because we expect
    # it to always work (and if it does not, it will raise).
    Repo.delete!(post)

    conn
    |> put_flash(:info, "Post deleted successfully.")
    |> redirect(to: post_path(conn, :index))
  end
  
  def add_comment(conn, %{"comment" => comment_params, "post_id" => post_id}) do
    changeset = Comment.changeset(%Comment{}, Map.put(comment_params, "post_id", post_id))
    post = Repo.get(Post, post_id) |> Repo.preload([:comments])

    if changeset.valid? do
      Repo.insert(changeset)

      conn
      |> put_flash(:info, "Comment added.")
      |> redirect(to: post_path(conn, :show, post))
    else
      render(conn, "show.html", post: post, changeset: changeset)
    end
  end
  
end
   

   

   
   створимо шаблон - web/templates/post/comment_form.html.eex
   

"результат"

   

   <%= form_for @changeset, @action, fn f -> %>
  <%= if f.errors != [] do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below:</p>
      <ul>
        <%= for {attr, message} <- f.errors do %>
          <li><%= humanize(attr) %> <%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
 
  <div class="form-group">
    <label>Name</label>
    <%= text_input f, :name, class: "form-control" %>
  </div>

  <div class="form-group">
    <label>Content</label>
    <%= textarea f, :content, class: "form-control" %>
  </div>

  <div class="form-group">
    <%= submit "Add comment", class: "btn btn-primary" %>
  </div>
<% end %>
   

   

   
   і включимо відображення цього шаблону в - web/templates/post/show.html.eex
   

"результат"

   

   <h2><%= @post.title %></h2>
<div>
  <%= @post.body %>
</div>

<%= render "comment_form.html", post: @post, changeset: @changeset,
                                action: post_post_path(@conn, :add_comment, @post) %>

<%= render "comments.html", post: @post %>

<%= link "Back", to: post_path(@conn, :index) %>
   

   

   
   тепер ми можемо створювати коментарі, залишилось їх показати
   створюємо web/templates/post/comments.html.eex
   

"результат"

   

   <h3> Comments: </h3>
<table class="table">
  <thead>
    <tr>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
<%= for comment <- @post.comments do %>
    <tr>
      <td><%= comment.name %></td>
      <td><%= comment.content %></td>
    </tr>
<% end %>
  </tbody>
</table>
   

   

   
   змінимо-допишемо шаблон виводу - web/templates/post/index.html.eex
   

"результат"

   

   <h2>Listing posts</h2>

<table class="table">
  <thead>
    <tr>
      <th>Title</th>
      <th>Comments</th>

      <th></th>
    </tr>
  </thead>
  <tbody>
<%= for {post, count} <- @posts do %>
    <tr>
      <td><%= post.title %></td>
      <td><%= count %></td>

      <td class="text-right">
        <%= link "Show", to: post_path(@conn, :show, post), class: "btn btn-default btn-xs" %>
        <%= link "Edit", to: post_path(@conn, :edit, post), class: "btn btn-default btn-xs" %>
        <%= link "Delete", to: post_path(@conn, :delete, post), method: :delete, class: "btn btn-danger btn-xs" %>
      </td>
    </tr>
<% end %>
  </tbody>
</table>

<%= link "New post", to: post_path(@conn, :new) %>
   

   

   
   подивимось що у нас получилось -- http://localhost:4000/posts (чи який у нас там домен+порт)
   
   https://github.com/221V/mini-blog
   
   за матеріалами -
   http://monterail.com/blog/2015/phoenix-blog/
   http://www.phoenixframework.org/docs/
   https://hexdocs.pm/phoenix/Phoenix.html
   та з допомогою Neb0 -
   http://www.cyberforum.ru/blogs/12078/

Дякую за увагу :)

3 Востаннє редагувалося 221VOLT (28.07.2016 17:27:32)

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

i18n, робота з gettext
   напишемо плагін plug, у якому беремо мову з accept-language -- https://github.com/221V/mini-blog/blob/ … /locale.ex
   підключимо його в router --
   

plug Blog.Plug.Locale, "en"

   
як працювати з gettext :
1) пишемо у коді виклики gettext всюди де потрібен переклад
наприклад

gettext "Welcome to Phoenix!"
gettext("Welcome to Phoenix!")
gettext "Welcome to %{name}", name: "Phoenix!"
gettext("Welcome to %{name}", name: "Phoenix!")
{:safe, gettext "A productive web framework that<br />does not compromise speed and maintainability."}

(остання конструкція - не перетворює html теги в їх текстове представлення, теги залишаються тегами - вони працюють :) )

2) генеруємо .pot файли з викликів gettext у коді

mix gettext.extract

( в .pot нічого редагувати не треба )

3) генеруємо .po файли -- тобто локалі, які власне і редактуються -- власне це і є мовні файли з перекладом-локалізацією

mix gettext.merge priv/gettext --locale ru
mix gettext.merge priv/gettext --locale ua

----------

   загалом отакий комміт получився - https://github.com/221V/mini-blog/commi … fafead41e6
   
   планую над цим "міні-блогом" попрацювати, раптом виросте з "міні"))
   далі буде... (напевно наступне - авторизація)

Подякували: Monolith, Djalin, leofun013

4

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

Реєстрація і авторизація (частина 1)
   
   Створимо модель для користувачів
   

$ mix phoenix.gen.model User users email:unique crypted_password

   
   і відредагуємо її
   virtual: true - означає що у таблиці це значення зберігатися не буде
   field :type, :integer - насправді додається у цю модель трошки пізніше - коли ми будемо створювати адміна
   

defmodule Blog.User do
  use Blog.Web, :model

  schema "users" do
    field :email, :string
    field :crypted_password, :string
    field :password, :string, virtual: true

    timestamps()
    field :type, :integer
  end
  
  @required_fields ~w(email password)
  @optional_fields ~w()

  @doc """
  Creates a changeset based on the `model` and `params`.
  
  If `params` are nil, an invalid changeset is returned
  with no validation performed.
  """
  def changeset(model, params \\ %{}) do
    model
    |> cast(params, @required_fields, @optional_fields)
    |> unique_constraint(:email)
    |> validate_format(:email, ~r/@/)
    |> validate_length(:password, min: 5)
  end

end

   
   створимо таблицю users в бд
   для цього згенеруємо міграцію
   

$ mix ecto.gen.migration create_user

   
   

defmodule Blog.Repo.Migrations.CreateUser do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :email, :string
      add :crypted_password, :string

      timestamps()
    end
    create unique_index(:users, [:email])

  end
end

   
   застосуємо зміни
 

$ mix ecto.migrate

   
   створимо сторінку реєстрації --
   додамо у \web\router.ex
   

get "/registration", RegistrationController, :new
   post "/registration", RegistrationController, :create

   можна ще зробити по-інакшому --
   

resources "/registration", RegistrationController, only: [:new, :create]

   проте мені більше подобається прозорість
   
   далі додамо в шаблон \web\template\layout\app.html.eex посилання на сторінку реєстрації
   

<%= link "Register", to: registration_path(@conn, :new) %>

   
   після цього створимо RegistrationController (\web\controllers\registration_controller.ex)
   
   

defmodule Blog.RegistrationController do
  use Blog.Web, :controller
  alias Blog.User

  def new(conn, _params) do
    render(conn, changeset: User.changeset(%User{}))
  end
  
  def create(conn, %{"user" => user_params}) do
    changeset = User.changeset(%User{}, user_params)
    
    case Blog.Registration.create(changeset, Blog.Repo) do
      {:ok, changeset} ->
        conn
        |> put_session(:uid, changeset.id)
        |> put_flash(:info, "Your account was creared")
        |> redirect(to: "/")
      {:error, changeset} ->
        #IO.inspect changeset
        conn
        |> put_flash(:info, "Unable to create account")
        |> render("new.html", changeset: changeset)
    end
  end
  
end

   
   у цьому контроллері ми прописали
   

|> put_session(:uid, changeset.id)

   для того щоб новозареєстрований користувач відразу був залогіненим
   
   створимо RegistrationView (web\views\registration_view.ex)
   
   

   defmodule Blog.RegistrationView do
  use Blog.Web, :view
end

   створимо шаблон /templates/registration/new.html.eex
   
   

<h2>Create an account</h2>

<%= form_for @changeset, registration_path(@conn, :create), fn f -> %>
  <%= if f.errors != [] do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below!</p>
    </div>
  <% end %>

  <div class="form-group">
    <label>Email</label>
    <%= email_input f, :email, class: "form-control" %>
    <%= error_tag f, :email %>
  </div>

  <div class="form-group">
    <label>Password</label>
    <%= password_input f, :password, class: "form-control" %>
    <%= error_tag f, :password %>
  </div>

  <div class="form-group">
    <%= submit "Signup", class: "btn btn-primary" %>
  </div>
<% end %>

   
   далі створимо модуль Registration (web\models\registration.ex)
   

   defmodule Blog.Registration do
  use Blog.Web, :model
  import Ecto.Changeset, only: [put_change: 3]

  def create(changeset, repo) do
    changeset
    |> put_change(:crypted_password, hashed_password(changeset.params["password"]))
    |> put_change(:type, 1)
    |> repo.insert()
  end

  defp hashed_password(password) do
    Comeonin.Bcrypt.hashpwsalt(password)
  end
  
end
   

   
   оскільки вище ми написали код з використанням Comeonin (для створення хешів паролів з допомогою алгоритма bcrypt) - нам потрібно встановити цю бібліотеку
   додаємо у \blog\mix.ex
   
   

   {:comeonin, "~> 2.5"}
   

   після
   

   {:cowboy, "~> 1.0"}
   

   
   після чого
   

$ mix deps.get

   
   
   додаємо сторінку входу і виходу в /web/routex.ex
   

get "/login", SessionController, :new
   post "/login", SessionController, :create
   delete "/logout", SessionController, :delete

   
   створимо SessionController (\web\controllers\session_controller.ex)
   
   

defmodule Blog.SessionController do
  use Blog.Web, :controller
  #alias Blog.User

  def new(conn, _params) do
    render(conn, "new.html")
  end
  
  def create(conn, %{"session" => session_params}) do
    case Blog.Session.login(session_params, Blog.Repo) do
      {:ok, user} ->
        #IO.inspect user
        conn
        |> put_session(:uid, user.id)
        |> put_flash(:info, "Logged in")
        |> redirect(to: "/")
      :error ->
        conn
        |> put_flash(:info, "Wrong email or password")
        |> render("new.html")
    end
  end
  
  def delete(conn, _) do
    conn
    |> delete_session(:uid)
    |> put_flash(:info, "Logged out")
    |> redirect(to: "/")
  end
  
end

   створимо шаблон web/templates/session/new.html.eex
   

<h2>Login</h2>

<%= form_for @conn, session_path(@conn, :create), [as: :session], fn f -> %>
  <div class="form-group">
    <label>Email</label>
    <%= email_input f, :email, class: "form-control" %>
  </div>

  <div class="form-group">
    <label>Password</label>
    <%= password_input f, :password, class: "form-control" %>
  </div>

  <div class="form-group">
    <%= submit "Login", class: "btn btn-primary" %>
  </div>
<% end %>
Подякували: 0xDADA11C7, leofun01, Betterthanyou, ostap34PHP4

5 Востаннє редагувалося 221VOLT (03.08.2016 16:20:14)

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

Реєстрація і авторизація (частина 2)
   
   створимо /web/views/session_view.ex
   

   defmodule Blog.SessionView do
  use Blog.Web, :view
  use Blog.Web, :controller
  
end

   тут
   

use Blog.Web, :controller

   означає що десь в шаблоні ми хочемо викликати функцію з контроллера (конкретно тут, здається, воно зайве,, крім того - є інший, напевно кращий спосіб це зробити -- прописати модуль-функції у модулі \web\web.ex)
   
   створимо Session модуль (web/models/session.ex)
   
   

defmodule Blog.Session do
  #use Blog.Web, :model
  alias Blog.User

  def login(params, repo) do
    user = repo.get_by(User, email: String.downcase(params["email"]))
    case authenticate(user, params["password"]) do
      true -> {:ok, user}
      _    -> :error
    end
  end

  defp authenticate(user, password) do
    case user do
      nil -> false
      _   -> Comeonin.Bcrypt.checkpw(password, user.crypted_password)
    end
  end
  
  def current_user(conn) do
    id = Plug.Conn.get_session(conn, :uid)
    if id, do: Blog.Repo.get(User, id)
  end
  
  #def logged_in?(conn), do: !!current_user(conn)
  def logged_in?(session_user) do
    case session_user do
      nil -> false
      _   -> true
    end
  end
  
end

   
   зробимо функції з контроллера доступними у view
   для цього допишемо у \web\web.ex в функцію view наступне
   

import Blog.Session, only: [current_user: 1, logged_in?: 1]

   
   тепер ми можемо дописати провірку у шаблоні \web\template\layout\app.html.eex --
   на предмет того, залогінений користувач чи ні, показувати йому посилання на реєстрацію чи на вихід
   
   зверху пропишемо
   

<% sess_user = current_user(@conn) %>

   і нижче власне саму перевірку
   

<%= if logged_in?(sess_user) do %>
     <span><%= sess_user.email %></span>
     <span><%= link "Logout", to: session_path(@conn, :delete), method: :delete %></span>
    <% else %>
     <span><%= link "Login",    to: "/login" %></span>
     <span><%= link "Register", to: registration_path(@conn, :new) %></span>
    <% end %>

   
   щоб передати дані нижче по шаблону - пропишемо
   

<%= render @view_module, @view_template, Map.put(assigns, :sess_user, sess_user) %>

   
   
   зміни до написаного раніше --
   написання постів -
   додав перевірку(пост може видаляти, писати, редагувати лише адмін)
   
   для того щоб працювала така переадресація-заглушка
   

put_status(conn, 403)
   |> render(Blog.ErrorView, "403.html", %{})

   потрібно додати функцію в модуль \web\views\error_view.ex
   

def render("403.html", _assigns) do
    "Access forbidden"
  end

   
   
   загалом код для функціоналу цих змін ми уже написали, а про таблицю бд - забули
   
   
   додамо новий стовпчик у таблицю users
   для цього згенеруємо міграцію
   

$ mix ecto.gen.migration add_fields_to_users

   
   знайдемо де вона згенерувалася і відредагуємо
   (\blog\priv\repo\migrations\xxxxxxxxx_add_fields_to_users.exs)
   
   

defmodule Blog.Repo.Migrations.AddFieldsToUsers do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :type, :smallint, null: false, default: 1
    end
  end
  
end

 
  застосуємо зміни
 

$ mix ecto.migrate

 
  у результаті у нас зявиться ще один стовпчик -- отакий получиться дамп якщо зробимо його зараз
 

DROP TABLE "public"."users";
CREATE TABLE "public"."users" (
"id" int4 DEFAULT nextval('users_id_seq'::regclass) NOT NULL,
"email" varchar(255),
"crypted_password" varchar(255),
"inserted_at" timestamp(6) NOT NULL,
"updated_at" timestamp(6) NOT NULL,
"type" int2 DEFAULT 1 NOT NULL
)
WITH (OIDS=FALSE);

 
  для тесту додамо ще один стовпчик
 
 

$ mix ecto.gen.migration add_test_to_users

   
   

defmodule Blog.Repo.Migrations.AddTestToUsers do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :test, :bigint, default: 1
    end
  end
  
end

   
   застосуємо зміни
 

$ mix ecto.migrate

 
  у результаті у нас зявиться ще один стовпчик -- отакий получиться дамп якщо зробимо його зараз
 
 

DROP TABLE "public"."users";
CREATE TABLE "public"."users" (
"id" int4 DEFAULT nextval('users_id_seq'::regclass) NOT NULL,
"email" varchar(255),
"crypted_password" varchar(255),
"inserted_at" timestamp(6) NOT NULL,
"updated_at" timestamp(6) NOT NULL,
"type" int2 DEFAULT 1 NOT NULL,
"test" int8 DEFAULT 1
)
WITH (OIDS=FALSE);

   
   тепер видалимо цей тестовий стовпчик
   
   

$ mix ecto.gen.migration del_test_to_users

   
   

defmodule Blog.Repo.Migrations.DelTestToUsers do
  use Ecto.Migration

  def change do
    alter table(:users) do
      remove :test
    end
  end
  
end

   
   застосуємо зміни
 

$ mix ecto.migrate

 
   очікуваний результат
   

DROP TABLE "public"."users";
CREATE TABLE "public"."users" (
"id" int4 DEFAULT nextval('users_id_seq'::regclass) NOT NULL,
"email" varchar(255),
"crypted_password" varchar(255),
"inserted_at" timestamp(6) NOT NULL,
"updated_at" timestamp(6) NOT NULL,
"type" int2 DEFAULT 1 NOT NULL
)
WITH (OIDS=FALSE);

   
   зробимо користувача з id 1 адміном
   
   

$ mix ecto.gen.migration make_adm

   
   

defmodule Blog.Repo.Migrations.MakeAdm do
  use Ecto.Migration

  def change do
    #Blog.Repo.update_all("users", set: [type: 1])
    
    %Blog.User{id: 1}
     |> Ecto.Changeset.change(type: 9)
     |> Blog.Repo.update
    
  end
  
end

 
  застосуємо зміни
 

$ mix ecto.migrate

 
  ще трошки про зміни написано раніше --
  у роутері (\web\router.ex) було вирішено зробити такі зміни
 

#resources "/posts", PostController do
  #  post "/comment", PostController, :add_comment
  #end
  
  resources "/posts", PostController
  post "/posts/:post_id/comment", PostController, :add_comment

  щоб позбутися "неапетитного" шляху post_post_path (його ви можете побачити у web\templates\post\show.html.eex) та для більшої прозорості
 
  такс, наче нічого важливо не забув,
  а решту дрібних зміни ви можете подивитись на гітхабі :)
 
  загалом отакий комміт получився -- https://github.com/221V/mini-blog/commi … b170189dc9
 
  за матеріалами -
   http://nithinbekal.com/posts/phoenix-authentication/
   http://meatherly.github.io/2015/05/11/p … ntication/
   http://www.phoenixframework.org/docs/
   https://hexdocs.pm/phoenix/Phoenix.html
   https://hexdocs.pm/ecto/Ecto.html
   https://hexdocs.pm/phoenix_html/Phoenix.HTML.html
   та з допомогою Neb0 -
   http://www.cyberforum.ru/blogs/12078/
 
   Дякую за увагу :)
   
   далі буде... (напевно наступне - оцінка постів, лайки комментів, рейтинги якісь [роздуми у спостеріганні структури пікабу,хабра,анекдот.ру,різних блогів,...] )
(+ поправлю ще реєстрацію і написання коментарі -- а то зараз просто база для спамера получається :D)

Подякували: 0xDADA11C7, leofun01, Betterthanyou, ostap34PHP4

6

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

Гарно, але як на мене це треба у якийсь блог пихати. Дуже не люблю коли стаття "розірвана" по постах.

7

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

А можливо цей фенікс заставити працювати з SQLite? Мені, наприклад, наразі потрібна дуже примітивна база з однією таблицею. Ну, по документації, схоже що його ORM уміє працювати поки лише з PostgreSQL, дивно. До речі, а можна тут відмовитися від ORM взагалі і використати прямі запити до файлу SQLite?

Подякували: 221VOLT1

8 Востаннє редагувалося 221VOLT (04.09.2016 20:30:18)

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

Master_Sergius написав:

А можливо цей фенікс заставити працювати з SQLite? Мені, наприклад, наразі потрібна дуже примітивна база з однією таблицею. Ну, по документації, схоже що його ORM уміє працювати поки лише з PostgreSQL, дивно. До речі, а можна тут відмовитися від ORM взагалі і використати прямі запити до файлу SQLite?

http://twentyeighttwelve.com/creating-a … th-sqlite/
"з коробки" - там ще мускуль є, а взагалі - там багато різного є на https://hex.pm/

---

можна, можна і ерлангові драйвери використовувати
наприклад https://github.com/alexeyr/erlang-sqlite3
чи ще які знайти (сам я лише mysql\postgresql користуюсь поки)


виклик функцій -
erlang

module:func()

elixir to erlang

:module.func
Подякували: Master_Sergius1

9 Востаннє редагувалося Master_Sergius (04.09.2016 22:52:25)

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

Ну добре, схоже, що є ось sqlite_ecto:

mix hex.search sqlite
esqlite     0.2.2
sqlite3     1.1.5
sqlite_ecto 1.1.0
sqlitex     1.0.0

То як його тепер прикрутити до проекту на феніксі? В кореневому каталозі в mix.exs дописати депенденсі? А як для ecto повідомити, що використовуємо? Чи ще щось потрібно?

Ну от, не вдається, навіть послабив версії (>= 0.0.0), воно перебирає всі можливі варіанти і в результаті все одно для кожного ось таке:

Failed to use "ecto" (version 2.0.4) because
  phoenix_ecto (version 3.0.1) requires ~> 2.0
  sqlite_ecto (version 1.1.0) requires ~> 1.1
  Locked to 2.0.4 in your mix.lock

Як з цим боротися? Чи ще воно аж настільки все сире?

Ех, таки ще сируватеньке. На гітхбі знайшов ішью, вони навіть дропнули генерацію фенікс-проекту з підтримкою sqlite, поки sqlite_ecto не буде підтримувати версію ecto 2.0
Ну хз, навіть. І хочеться поближче помацати цей еліксир/фенікс, а він ще колеться...

Подякували: 221VOLT1

10

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

можна встановити екто нижчої версії, можна почекати, можна ще варіанти пошукати

11

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

221VOLT написав:

можна встановити екто нижчої версії, можна почекати, можна ще варіанти пошукати

Та вже натравив на Postgres, бавлюся... На моєму дохлому нетбуку компіляція відбувається дуже довго... Зате потім працює на раз два

Подякували: 221VOLT1

12 Востаннє редагувалося Master_Sergius (12.09.2016 15:40:43)

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

А можна в двох словах як краще задати якусь глобальну змінну, яка буде доступна з будь-якого контроллеру чи вьюшки? Напевно, це має бути в config/config.exs? І як до неї доступатися?

І ще одне: чи можна якось в еліксирі зробити ланцюжок функцій, щоб не писати ось так:

String.to_integer String.trim "4\n"

, а якось отак:

"4\n".trim.to_integer

або  хоча би отак:

String.trim.to_integer "4\n"

Хоча, по стайл гайдах рекомендують через пайп https://github.com/levionessa/elixir_style_guide

Подякували: 221VOLT1

13 Востаннє редагувалося 221VOLT (13.09.2016 00:37:00)

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

Master_Sergius написав:

А можна в двох словах як краще задати якусь глобальну змінну, яка буде доступна з будь-якого контроллеру чи вьюшки? Напевно, це має бути в config/config.exs? І як до неї доступатися?

І ще одне: чи можна якось в еліксирі зробити ланцюжок функцій, щоб не писати ось так:

String.to_integer String.trim "4\n"

, а якось отак:

"4\n".trim.to_integer

або  хоча би отак:

String.trim.to_integer "4\n"

Хоча, по стайл гайдах рекомендують через пайп https://github.com/levionessa/elixir_style_guide

import у web.ex
можна написати plug який добавлятиме змінну у conn,
можна записати змінну у сесію,
можна зчитувати з файла,
можна тримати у памяті - як повідомлення у процесі,
напевно й ще є варіанти -- то перше що прийшло в голову :)

"4\n"
|> String.trim
|> String.to_integer
Подякували: ostap34PHP, Master_Sergius2

14 Востаннє редагувалося 221VOLT (23.10.2016 14:02:56)

Re: Пишемо міні-блог на elixir-phoenix [Стаття]

https://github.com/221V/mini-blog/commi … 0bc8b05310
отакий комміт получився опівнічний:)

причесав міграції, поправив коментарі, додав нікнейм, (+ favicon),
у процесі - логін не тільки по емейлу а ще й по нікнейму,
у планах - зробити можливість коментувати коментарі, оцінювати пости та коменти,
і звісно, дизайн до всього гарного функціоналу :)

UPD. доробив вхід по нікнейму і емейлу
отакий комміт https://github.com/221V/mini-blog/commi … f443957e96

далі буде...

Подякували: Betterthanyou, Monolith, ostap34PHP3