Skip to content

Instantly share code, notes, and snippets.

@mazz
Last active November 8, 2019 04:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mazz/8e5e91bbaa99bfcca2197e29564a4a40 to your computer and use it in GitHub Desktop.
Save mazz/8e5e91bbaa99bfcca2197e29564a4a40 to your computer and use it in GitHub Desktop.
defmodule Db.Schema.Channel do
use Ecto.Schema
import Ecto.Changeset
alias Db.Type.ChannelHashId
@derive {Jason.Encoder, only: [
:basename,
:ordinal,
:small_thumbnail_path,
:med_thumbnail_path,
:large_thumbnail_path,
:banner_path,
:uuid,
:org_id,
:hash_id,
:updated_at,
:inserted_at
]}
schema "channels" do
field :basename, :string
field :ordinal, :integer
field :small_thumbnail_path, :string
field :med_thumbnail_path, :string
field :large_thumbnail_path, :string
field :banner_path, :string
field :uuid, Ecto.UUID
field :org_id, :id
field :hash_id, :string
has_many :playlists, Db.Schema.Playlist
timestamps(type: :utc_datetime)
# timestamps()
end
@doc false
def changeset(channel, attrs) do
channel
|> changeset_generate_hash_id()
|> cast(attrs, [
:uuid,
:ordinal,
:basename,
:large_thumbnail_path,
:med_thumbnail_path,
:small_thumbnail_path,
:banner_path,
:org_id,
:hash_id
])
|> validate_required([
:uuid,
:ordinal,
:basename,
:large_thumbnail_path,
:med_thumbnail_path,
:small_thumbnail_path,
:banner_path,
:org_id,
:hash_id
])
end
@doc """
Generate hash ID for channels
## Examples
iex> Db.Schema.MediaItem.changeset_generate_hash_id(%Db.Schema.Video{id: 42, hash_id: nil})
#Ecto.Changeset<action: nil, changes: %{hash_id: \"4VyJ\"}, errors: [], data: #Db.Schema.Video<>, valid?: true>
"""
def changeset_generate_hash_id(channel) do
change(channel, hash_id: ChannelHashId.encode(channel.id))
end
end
defmodule Db.Type.ChannelHashId do
@moduledoc """
Convert a media item integer id to hash
"""
defmodule InvalidChannelHashError do
@moduledoc """
Exception throwed when hash is not valid
"""
defexception plug_status: 404, message: "Not found", conn: nil, router: nil
end
@coder Hashids.new(
min_len: 4,
alphabet: "23456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKMNOPQRSTUVWXYZ",
salt: "F41thfu1W0rDCh4Nn€L"
)
@doc """
Encode a given id
## Examples
iex> Db.Type.ChannelHashId.encode(42)
"4VyJ"
"""
def encode(id) do
Hashids.encode(@coder, id)
end
@doc """
Decode a given hash
## Examples
iex> Db.Type.ChannelHashId.decode("JbOz")
{:ok, 1337}
iex> Db.Type.ChannelHashId.decode("€€€€€€€€€€€€€€€€€")
{:error, :invalid_input_data}
"""
def decode(hash) do
case do_decode(hash) do
{:ok, [id]} -> {:ok, id}
error -> error
end
end
@doc """
Decode a given hash. Raise if hash is invalid
## Examples
iex> Db.Type.ChannelHashId.decode!("JbOz")
1337
iex> catch_throw(Db.Type.ChannelHashId.decode!("€€€"))
Db.Type.ChannelHashId.InvalidChannelHashError
"""
def decode!(hash) do
case do_decode(hash) do
{:ok, [id]} -> id
_error -> throw(InvalidChannelHashError)
end
end
defp do_decode(hash),
do: Hashids.decode(@coder, hash)
end
```
[error] #PID<0.789.0> running FaithfulWordApi.Endpoint (connection #PID<0.788.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: POST //v1.3/channels/add
** (exit) an exception was raised:
** (FunctionClauseError) no function clause matching in Hashids.encode/2
(hashids) lib/hashids.ex:82: Hashids.encode(%Hashids{a_len: 40, alphabet: 'yaDkQqw57BMjE4gmzKXrYvdNAp6WPbO3VRGJ8o9e', g_len: 4, guards: 'Zx2n', min_len: 4, s_len: 13, salt: [70, 52, 49, 116, 104, 102, 117, 49, 87, 48, 114, 68, 67, 104, 52, 78, 110, 8364, 76], seps: 'sUCTucHtifhSF'}, nil)
(db) lib/db_schema/channel.ex:79: Db.Schema.Channel.changeset_generate_hash_id/1
(db) lib/db_schema/channel.ex:41: Db.Schema.Channel.changeset/2
(faithful_word_api) lib/faithful_word_api/controllers/api/v1.3/v13.ex:256: FaithfulWordApi.V13.add_channel/7
(faithful_word_api) lib/faithful_word_api/controllers/api/channel_controller.ex:22: FaithfulWordApi.ChannelController.addv13/2
(faithful_word_api) lib/faithful_word_api/controllers/api/channel_controller.ex:1: FaithfulWordApi.ChannelController.action/2
(faithful_word_api) lib/faithful_word_api/controllers/api/channel_controller.ex:1: FaithfulWordApi.ChannelController.phoenix_controller_pipeline/2
(phoenix) lib/phoenix/router.ex:288: Phoenix.Router.__call__/2
(faithful_word_api) lib/faithful_word_api/endpoint.ex:1: FaithfulWordApi.Endpoint.plug_builder_call/2
(faithful_word_api) lib/plug/debugger.ex:122: FaithfulWordApi.Endpoint."call (overridable 3)"/2
(faithful_word_api) lib/faithful_word_api/endpoint.ex:1: FaithfulWordApi.Endpoint.call/2
(phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4
(cowboy) /Users/michael/src/phx/faithfulword-phx/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
(cowboy) /Users/michael/src/phx/faithfulword-phx/deps/cowboy/src/cowboy_stream_h.erl:296: :cowboy_stream_h.execute/3
(cowboy) /Users/michael/src/phx/faithfulword-phx/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.request_process/3
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
```
@mazz
Copy link
Author

mazz commented Nov 8, 2019

add channel code:

  def add_channel(
    ordinal,
    basename,
    small_thumbnail_path,
    med_thumbnail_path,
    large_thumbnail_path,
    banner_path,
    org_id) do
    Channel.changeset(%Channel{}, %{
      ordinal: ordinal,
      basename: basename,
      small_thumbnail_path: small_thumbnail_path,
      med_thumbnail_path: med_thumbnail_path,
      large_thumbnail_path: large_thumbnail_path,
      banner_path: banner_path,
      org_id: org_id,
      uuid: Ecto.UUID.generate()
    })
    |> Repo.insert()
  end

@mazz
Copy link
Author

mazz commented Nov 8, 2019

Solution:

was passing a nil struct to .encode() because I needed to insert the item into the db before adding the hashid. This was because the item needs a .id to generate the hash. The solution chosen was to a) remove :hash_id from validate_required and b) use Ecto Multi to run the changeset and then update the insert with the hashid:

    changeset = Channel.changeset(%Channel{}, %{
      ordinal: ordinal,
      basename: basename,
      small_thumbnail_path: small_thumbnail_path,
      med_thumbnail_path: med_thumbnail_path,
      large_thumbnail_path: large_thumbnail_path,
      banner_path: banner_path,
      org_id: org_id,
      uuid: Ecto.UUID.generate()
      })

    Multi.new()
    |> Multi.insert(:item_without_hash_id, changeset)
    |> Multi.run(:channel, fn _repo, %{item_without_hash_id: channel} ->
      channel
      |> Channel.changeset_generate_hash_id()
      |> Repo.update()
    end)
    |> Repo.transaction()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment