I have a json/map field in database which can actually holds 3 kinds of objects based on type field define in table
so structure is like this
field(:type, :string) => user/player/admin
field(:object, :map) => embedded schema needed here. and it can be any of the three schemas ie user/admin/player
So is this possible I can have the flexibility of mysql and beauty/support of Ecto embeds.
You will have to manually convert the json object to an embedded schema based off of the type. This can easily be accomplished with a transform.
defmodule EnrichedUser do
defmodule User do
defstruct type: nil, name: ""
def new(record), do: %User{type: record.type}
end
defmodule Player do
defstruct type: nil, name: ""
def new(record), do: %Player{type: record.type}
end
defmodule Admin do
defstruct type: nil, name: ""
def new(record), do: %Admin{type: record.type}
end
def transform(record = %MyDatabaseUser{type: type}) do
case type do
:user -> User.new(record)
:player -> Player.new(record)
:admin -> Admin.new(record)
_ -> {:error, :unhandled_type}
end
end
end
# Usage example
User |> Repo.all() |> Enum.map(&EnrichedUser.transform/1)
This has the added benefit of decoupling your application logic from your database model. Your app will now pass around an EnricedUser.X struct which will server as the contract between services instead of the Ecto schema that will change when you make database changes.
Related
I have the following closure:
def get!(Item, id) do
Enum.find(
#items,
fn(item) -> item.id == id end
)
end
As I believe this looks ugly and difficult to read, I'd like to give this a name, like:
def get!(Item, id) do
defp has_target_id?(item), do: item.id = id
Enum.find(#items, has_target_id?/1)
end
Unfortunately, this results in:
== Compilation error in file lib/auction/fake_repo.ex ==
** (ArgumentError) cannot invoke defp/2 inside function/macro
(elixir) lib/kernel.ex:5238: Kernel.assert_no_function_scope/3
(elixir) lib/kernel.ex:4155: Kernel.define/4
(elixir) expanding macro: Kernel.defp/2
lib/auction/fake_repo.ex:28: Auction.FakeRepo.get!/2
Assuming it is possible, what is the correct way to do this?
The code you posted has an enormous amount of syntax errors/glitches. I would suggest you start with getting accustomed to the syntax, rather than trying to make Elixir better by inventing the things that nobody uses.
Here is the correct version that does what you wanted. The task might be accomplished with an anonymous function, although I hardly see a reason to make a perfectly looking idiomatic Elixir look ugly.
defmodule Foo do
#items [%{id: 1}, %{id: 2}, %{id: 3}]
def get!(id) do
has_target_id? = fn item -> item.id == id end
Enum.find(#items, has_target_id?)
end
end
Foo.get! 1
#⇒ %{id: 1}
Foo.get! 4
#⇒ nil
You can do this:
def get!(Item, id) do
Enum.find(
#items,
&compare_ids(&1, id)
)
end
defp compare_ids(%Item{}=item, id) do
item.id == id
end
But, that's equivalent to:
Enum.find(
#items,
fn item -> compare_ids(item, id) end
)
and may not pass your looks ugly and difficult to read test.
I was somehow under the impression Elixir supports nested functions?
Easy enough to test:
defmodule A do
def go do
def greet do
IO.puts "hello"
end
greet()
end
end
Same error:
$ iex a.ex
Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
** (ArgumentError) cannot invoke def/2 inside function/macro
(elixir) lib/kernel.ex:5150: Kernel.assert_no_function_scope/3
(elixir) lib/kernel.ex:3906: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
a.ex:3: A.go/0
wouldn't:
defp compare_ids(item, id), do: item.id == id
be enough? Is there any advantage to including %Item{} or making
separate functions for returning both true and false conditions?
What you gain by specifying the first parameter as:
func(%Item{} = item, target_id)
is that only an Item struct will match the first parameter. Here is an example:
defmodule Item do
defstruct [:id, :name, :description]
end
defmodule Dog do
defstruct [:id, :name, :owner]
end
defmodule A do
def go(%Item{} = item), do: IO.inspect(item.id, label: "id: ")
end
In iex:
iex(1)> item = %Item{id: 1, name: "book", description: "old"}
%Item{description: "old", id: 1, name: "book"}
iex(2)> dog = %Dog{id: 1, name: "fido", owner: "joe"}
%Dog{id: 1, name: "fido", owner: "joe"}
iex(3)> A.go item
id: : 1
1
iex(4)> A.go dog
** (FunctionClauseError) no function clause matching in A.go/1
The following arguments were given to A.go/1:
# 1
%Dog{id: 1, name: "fido", owner: "joe"}
a.ex:10: A.go/1
iex(4)>
You get a function clause error if you call the function with a non-Item, and the earlier an error occurs, the better, because it makes debugging easier.
Of course, by preventing the function from accepting other structs, you make the function less general--but because it's a private function, you can't call it from outside the module anyway. On the other hand, if you wanted to call the function on both Dog and Item structs, then you could simply specify the first parameter as:
|
V
func(%{}=thing, target_id)
then both an Item and a Dog would match--but not non-maps.
What you gain by specifying the first parameter as:
|
V
func(%Item{id: id}, target_id)
is that you let erlang's pattern matching engine extract the data you need, rather than calling item.id as you would need to do with this definition:
func(%Item{}=item, target_id)
In erlang, pattern matching in a parameter list is the most efficient/convenient/stylish way to write functions. You use pattern matching to extract the data that you want to use in the function body.
Going even further, if you write the function definition like this:
same variable name
| |
V V
func(%Item{id: target_id}, target_id)
then erlang's pattern matching engine not only extracts the value for the id field from the Item struct, but also checks that the value is equal to the value of the target_id variable in the 2nd argument.
Defining multiple function clauses is a common idiom in erlang, and it is considered good style because it takes advantage of pattern matching rather than logic inside the function body. Here's an erlang example:
get_evens(List) ->
get_evens(List, []).
get_evens([Head|Tail], Results) when Head rem 2 == 0 ->
get_evens(Tail, [Head|Results]);
get_evens([Head|Tail], Results) when Head rem 2 =/= 0 ->
get_evens(Tail, Results);
get_evens([], Results) ->
lists:reverse(Results).
My mix file contains
{:guardian, "~> 1.0"},
{:guardian_db, "~> 1.1"},
and config contains
config :my_app, MyApp.Guardian,
issuer: "my_app",
ttl: {30, :days},
allowed_drift: 2000,
verify_issuer: true,
# mix guardian.gen.secret (to get a key for dev and prod envs)
secret_key: "yKwVGXFyH6nbiE+ELRMLYjCDC3QughF02LN+xPlB7z2loDKeNuBJ6RIUdTMBul23"
config :guardian, Guardian.DB,
repo: Qserv.BaseRepo,
schema_name: "sessions", # default
token_types: ["refresh_token"], # store all token types if not set
sweep_interval: 60
and my application has this line
worker(Guardian.DB.Token.SweeperServer, []),
and My migration for sessions table
defmodule MyApp.Repo.Migrations.CreateTable.Auth.Sessions do
use Ecto.Migration
#table :sessions
def change do
create table(#table, primary_key: false) do
add :jti, :string, primary_key: true
add :aud, :string, primary_key: true
add :typ, :string
add :iss, :string
add :sub, :string
add :exp, :bigint
add :jwt, :text
add :claims, :map
timestamps()
end
create index(#table, [:jwt])
create index(#table, [:sub])
create index(#table, [:jti])
end
end
and I have this file
defmodule MyApp.Guardian do
use Guardian, otp_app: :my_app
def subject_for_token(resource, _claims) do
sub = to_string(resource.id)
{:ok, sub}
end
def subject_for_token(_, _) do
{:error, :reason_for_error}
end
def resource_from_claims(claims) do
resource = %{id: 1}
{:ok, resource}
end
def resource_from_claims(_claims) do
{:error, :reason_for_error}
end
def after_encode_and_sign(resource, claims, token, _options) do
with {:ok, _} <- Guardian.DB.after_encode_and_sign(resource, claims["typ"], claims, token) do
{:ok, token}
else whatever ->
IO.inspect whatever
end
end
def on_verify(claims, token, _options) do
with {:ok, _} <- Guardian.DB.on_verify(claims, token) do
{:ok, claims}
end
end
def on_revoke(claims, token, _options) do
with {:ok, _} <- Guardian.DB.on_revoke(claims, token) do
{:ok, claims}
end
end
end
Everything works great and I am able to login and get token successfully except that guardian db is unable to insert record into database. I login like this
MyApp.Guardian.encode_and_sign(%{id: 1}, %{key: :value}, token_type: "cus")
I also got printed after_encode_and_sign, resource and claims correctly guardian db hooks, but token details not being inserted into database. What might be wrong here
In the configuration you specified token_types: ["refresh_token"].
By calling Guardian.encode you create access token that's why nothing is persisted in database.
To persist all types of tokens remove this line.
I'm just trying to use Postgrex without any kind of ecto setup, so just the example from the documentation readme.
Here is what my module looks like:
defmodule Receive do
def start(_type, _args) do
{:ok, pid} = Postgrex.start_link(
hostname: "localhost",
username: "john",
# password: "",
database: "property_actions",
extensions: [{Postgrex.Extensions.JSON}]
)
Postgrex.query!(
pid,
"INSERT INTO actions (search_terms) VALUES ($1)",
[
%{foo: 'bar'}
]
)
end
end
when I run the code I get
** (RuntimeError) type `json` can not be handled by the types module Postgrex.DefaultTypes, it must define a `:json` library in its options to support JSON types
Is there something I'm not setting up correctly? From what I've gathered in the documentation, I shouldn't even need to have that extensions line because json is handled by default.
On Postgrex <= 0.13, you need to define your own types:
Postgrex.Types.define(MyApp.PostgrexTypes, [], json: Poison)
and then when starting Postgrex:
Postgrex.start_link(types: MyApp.PostgrexTypes)
On Postgrex >= 0.14 (currently master), it was made easier:
config :postgrex, :json_library, Poison
Doing upsert is common in my app and I want to implement the cleanest and simple way to implement upsert.
Should I use fragments to implement native sql upsert?
Any idiomatic ecto way to do upsert?
You can use Ecto.Repo.insert_or_update/2, please note that for this to work, you will have to load existing models from the database.
model = %Post{id: 'existing_id', ...}
MyRepo.insert_or_update changeset
# => {:error, "id already exists"}
Example:
result =
case MyRepo.get(Post, id) do
nil -> %Post{id: id} # Post not found, we build one
post -> post # Post exists, using it
end
|> Post.changeset(changes)
|> MyRepo.insert_or_update
case result do
{:ok, model} -> # Inserted or updated with success
{:error, changeset} -> # Something went wrong
end
In my case insert_or_update raised an error due to the unique index constraint 🤔
What did work for me was Postgres v9.5 upsert through on_conflict parameter:
(considering unique column is called user_id)
changeset
|> MyRepo.insert(
on_conflict: :replace_all,
conflict_target: :user_id
)
If you're looking to upsert by something other than id, you can swap in get_by for get like this:
model = %User{email: "existing_or_new_email#heisenberg.net", name: "Cat", ...}
model |> User.upsert_by(:email)
# => {:found, %User{...}} || {:ok, %User{...}}
defmodule App.User do
alias App.{Repo, User}
def upsert_by(%User{} = record_struct, selector) do
case User |> Repo.get_by({selector, record_struct |> Map.get(selector)}) do
nil -> %User{} # build new user struct
user -> user # pass through existing user struct
end
|> User.changeset(record_struct |> Map.from_struct)
|> Repo.insert_or_update
end
end
On the off chance you're looking for a flexible approach that works across models and for multiple selectors (ie country + passport number), check out my hex package EctoConditionals!
I'm trying to seed the database for my Phoenix application using the response from another api. I don't understand how to parse the response to create new object from it. I'm using HTTPoisin and Poison as of right now
seed.ex
alias NewsApplication.Article
HTTPoison.start
url = "rails_application.com/articles/index"
case HTTPoison.get(url) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
articles = Poison.decode!(body["articles"])
Enum.fetch(articles, 1, fn(a) -> IO.puts a end)
{:ok, %HTTPoison.Response{status_code: 404}} ->
IO.puts "Not found :("
{:error, %HTTPoison.Error{reason: reason}} ->
IO.inspect reason
end
web/model/article.ex
updated
defmodule NewsApplication.Article do
use Ecto.Model
schema "articles" do
field :label, :string
field :slug, :string
field :full_path, :string
field :content_cache, :string
field :position, :integer, default: 0
field :children_count, :integer, default: 0
field :is_published, :boolean, default: false
field :is_shared, :boolean, default: false
field :featured, :boolean, default: false
field :score, :integer
timestamps
end
end
sample response
{"articles":
[{
"article":{
"id":436,
"updated":"2015-08-14T11:51:21.931Z",
"title":"Celebrating It's 50th Issue",
"numberOfViews":0,
"numberOfFavorites":2,
"imageURLs":["http://.../images/1549/original/axye.png"],
"tags":["Company News"],
"isFeatured":false,
"isPublished":true,
"published":"2015-07-28T17:00:00.000Z"
}
}]
}
It looks like you're almost there, but are missing a couple of things.
You'll want to iterate through each of the article responses using Enum.each and save these to the database like this:
Article.changeset(%Article{}, json)
|> Repo.insert
putting it all together, it'd be something like this:
Enum.each articles_json, fn(article) ->
Article.changeset(%Article{}, json)
|> Repo.insert
end
This won't handle any errors on Repo.insert if the changeset is invalid, but it'll work otherwise.
I'd recommend having a read through the Phoenix docs for models for further reference – specifically have a look at how they're structuring create actions.