How can I access primary_key/id while building the Ecto changeset - phoenix-framework

I want to build some ancestry features with a materialized path.
So when I create the first item (it would probably get id of 1). the ancestry item would be 1/. Then when I create an item that is the child of that, it would have ancestry of 1/2/ or parent_ancestry + :id + / but I can't access the :id in the changeset, is there any way to achieve this?
I expect:
changeset = Model.changeset(%Model{}, %{thing: "thing"})
{:ok, model} = Repo.insert(changeset)
%Model{id: 1, thing: "thing", ancestry: "1/"}
changeset2 = Model.changeset(%Model{}, %{thing: "thing2", parent_id: 1})
{:ok, model} = Repo.insert(changeset2)
%Model{id: 2, thing: "thing2", ancestry: "1/2/"} # I know how to grab the parent ancestry here, I know it's not just the parent_id. My issue is the current model's id

Related

Looping a text file creating a nested hash from data and updating the hash

I am trying to process a series of sports results that get supplied as a text file.
Each line contains the name of the home team, the name of the away team, and the result.
I need to then update a league table to show a number of stats for each team.
I was initially trying it as a class but think it might be simpler as a nested hash, as I don't think I can set new objects without knowing the team names, and this could change later, for example if I get data in for a new team playing I think it's easier to add it to a hash as this would hopefully be a unique name.
None of the answers I have found here seem to fix my issue. Is this because I am going about it the wrong way?
example input:
``file =<<~MSG
Team A, Team B, win
Team C, Team A, draw
Team C, Team B, lose
...
MSG``
so I read each line:
``file.each_line do |score_line|
match_info = score_line.delete!("\n").split(';') # [<home>,<away>,<score>]
home = match_info[0]
away = match_info[1]
score = match_info[2]
...
end``
I now have each team and the score to play with. This is where I am stumped.
I am trying to create a nested hash the first level being the team name the next level being the stats.
For example:
``=> { 'Team A' => {:matches_played=> 2, :wins=> 1, :draw=> 1, lose=> 0},
'Team B' => {:matches-played=> 2, :wins=> 1, :draw=> 0, lose=> 1},
...``
If I create a simple hash of one level, (e.g. Team => matches played) I have no trouble updating the hashes. For some reason as soon as I try to update the second level I get problems.
As the info is coming in I need to update the team stats, this could be as the home or away team, so I am using the variables home and away from the above example. To avoid re-setting the initial values of the hash, I have tried using #unless which just returns an empty hash.
For example:
`` unless table.has_key?(home)
table[home] = {:matches_played=> 1, :wins=> 0, :draw=> 0, lose=> 0}
end
unless table.has_key?(away)
table[away] = {:matches_played=> 1, :wins=> 0, :draw=> 0, lose=> 0}
end``
the idea was to then update the values of the keys in the second level depending on the score.
Currently I can't even get the second hash implemented, although the programme runs without errors, when I print out the hash I get => {}. Without the #unless the hash just keeps getting the initial values reset with each line read.
I am new to Ruby but feel that I will always need to create/access nested hashes outside of a database so trying to learn.
Assuming that after processing your file / input, you have an array like this:
results = [
["Team A", "Team B", "win"],
["Team C", "Team A", "draw"],
["Team C", "Team B", "lose"]
]
You could create a hash using a default proc which sets the defaults for a new entry:
table = Hash.new { |h, k| h[k] = { matches_played: 0, wins: 0, draw: 0, lose: 0 } }
Now, you can process the above results in a loop like this:
results.each do |home, away, score|
table[home][:matches_played] += 1
table[away][:matches_played] += 1
case score
when 'win'
table[home][:wins] += 1
table[away][:lose] += 1
when 'lose'
table[home][:lose] += 1
table[away][:wins] += 1
when 'draw'
table[home][:draw] += 1
table[away][:draw] += 1
end
end
The default proc ensures that each sub-hash is present with values initially set to 0.
Instead of a default proc, you could also conditionally assign each initial hash explicitly:
results.each do |home, away, score|
table[home] ||= { matches_played: 0, wins: 0, draw: 0, lose: 0 }
table[away] ||= { matches_played: 0, wins: 0, draw: 0, lose: 0 }
# ...
end
Either of the above results in the following table:
{
"Team A"=>{:matches_played=>2, :wins=>1, :draw=>1, :lose=>0},
"Team B"=>{:matches_played=>2, :wins=>1, :draw=>0, :lose=>1},
"Team C"=>{:matches_played=>2, :wins=>0, :draw=>1, :lose=>1}
}

Efficiently resolving belongs_to relationship in elixir dataloader?

Is it possible to use elixir dataloader to query a belongs_to relationship efficiently? It seems that the load is querying all of the items it needs, but the get is returning the first value of the loaded items regardless of which single item it actually needs. This is the code that I am using now:
field :node, :node_object, resolve: fn parent, _, %{context: %{loader: loader}} ->
# parent.node_id = 1, but concurrently also another parent.node_id = 5
loader
|> Dataloader.load(NodeContext, :node, parent) # loads node_id 5 and 1
|> on_load(fn loader ->
loader
|> Dataloader.get(NodeContext, :node, parent) # always returns the node with id = 5
|> (&{:ok, &1}).()
end)
end
My current work around is to use the following code, but it makes the code much uglier and unfriendly with the Ecto schemas since I need to explicitly specify the node schema and node_id field of the parent schema here instead of letting dataloader infer it from the existing ecto schemas:
field :node, :node_object, resolve: fn parent, _, %{context: %{loader: loader}} ->
loader
|> Dataloader.load(NodeContext, {:one, NodeSchema}, id: parent.node_id)
|> on_load(fn loader ->
loader
|> Dataloader.get(NodeContext, {:one, NodeSchema}, id: parent.node_id)
|> (&{:ok, &1}).()
end)
end
I was able to fix this by making the node_id a primary_key of the parent schema like this:
defmodule MyApp.ParentSchema do
use Ecto.Schema
alias MyApp.NodeSchema
#primary_key false
embedded_schema do
belongs_to :node, NodeSchema, primary_key: true
end
end
I'm not sure if this is intended behavior for the dataloader since it seems like the primary_key check should happen on the child object instead of the parent object.

With Ecto, validate that a changeset with 2 different related models have the same parent model

In my app I have a method to create a new response. A response has a belongs_to relationship to both a player and match.
In addition player and match both have a belongs_to relationship to a team.
It looks like this:
When inserting a new response I want to validate that the player and match having the player_id and match_id foreign keys in the changeset belong to the same team.
Currently I'm achieving this as follows. First, define a custom validation that checks the records belonging to the foreign keys:
def validate_match_player(changeset) do
player_team =
Player
|> Repo.get(get_field(changeset, :player_id))
|> Map.get(:team_id)
match_team =
Match
|> Repo.get(get_field(changeset, :match_id))
|> Map.get(:team_id)
cond do
match_team == player_team -> changeset
true -> changeset |> add_error(:player, "does not belong to the same team as the match")
end
end
and the use the validation as part of the changeset:
def changeset(model, params \\ %{}) do
model
|> cast(params, [:player_id, :match_id, :message])
|> validate_required([:player_id, :match_id, :message])
|> foreign_key_constraint(:match_id)
|> foreign_key_constraint(:player_id)
|> validate_match_player()
|> unique_constraint(
:player,
name: :responses_player_id_match_id_unique,
message: "already has an response for this match"
)
end
This works fine but involves a couple of extra SQL queries to look up the related records in order to get their team_id foreign keys to compare them.
Is there a nicer way to do this, perhaps using constraints, that avoids the extra queries?
I have two possible improvements:
Application level solution: instead of two queries, you just query once.
Database level solution: you create a trigger for the check in the database.
Application Level Solution
Right now you have two queries for checking that player and match belong to the same team. That means two round trips to the database. You could reduce this by half if you use just one query e.g. given the following query:
SELECT COUNT(*)
FROM players AS p
INNER JOIN matches AS m
ON p.team_id = m.team_id
WHERE p.id = NEW.player_id AND m.id = NEW.match_id
you would change your function as follows:
def validate_match_player(changeset) do
player_id = get_field(changeset, :player_id)
match_id = get_field(changeset, :match_id)
[result] =
Player
|> join(:inner, [p], m in Match, on: p.team_id == m.team_id)
|> where([p, m], p.id == ^player_id and m.id == ^match_id)
|> select([p, m], %{count: count(p.id)})
|> Repo.all()
case result do
%{count: 0} ->
add_error(changeset, :player, "does not belong to the same team as the match")
_ ->
changeset
end
end
Database Level Solution
I'm assuming you're using PostgreSQL, so my answer will correspond to what you can find in the PostgreSQL manual.
There's no (clean) way to define a constraint in the table that does this. Constraints can only access the table where they're defined. Some constraints can only access the column from what they're defined and nothing more (CHECK CONSTRAINT).
The best approach would be writing a trigger for validating both fields e.g:
CREATE OR REPLACE FUNCTION trigger_validate_match_player()
RETURNS TRIGGER AS $$
IF (
SELECT COUNT(*)
FROM players AS p
INNER JOIN matches AS m
ON p.team_id = m.team_id
WHERE p.id = NEW.player_id AND m.id = NEW.match_id
) = 0
THEN
RAISE 'does not belong to the same team as the match'
USING ERRCODE 'invalid_match_player';
END IF;
RETURN NEW;
$$ LANGUAGE plpgsql;
CREATE TRIGGER responses_validate_match_player
BEFORE INSERT OR UPDATE ON responses
FOR EACH ROW
EXECUTE PROCEDURE trigger_validate_match_player();
The previous trigger will raise an exception when it fails. This also means Ecto will raise an exception. You can see how to handle this exception here.
In the end, maintaining triggers is not easy unless you're using something like sqitch for database migrations.
PS: If you're curious, the very dirty way of doing this in a CHECK constraint is by defining a PostgreSQL function that basically bypasses the limitation. I wouldn't recommend it.
I hope this helps :)

Linq2Entities Equivalent Query for Parent/Child Relationship, With All Parents and Children, Filtering/Ordering Children

So the question is ridiculously long, so let's go to the code. What's the linq2entities equivalent of the following Sql, given entities (tables) that look like:
Parent
---
parent_id
parent_field1
Child
--
child_id
parent_id
child_field1
child_field2
The sql:
select p.*, c.*
from parent p
inner join p on
p.parent_id = child.parent_id
where
c.child_field1 = some_appropriate_value
order by
p.parent_field1
c.child_field2
L2E let's you do .include() and that seems like the appropriate place to stick the ordering and filtering for the child, but the include method doesn't accept an expression (why not!?). So, I'm guessing this can't be done right now, because that's what a lot of articles say, but they're old, and I'm wondering if it's possible with EF6.
Also, I don't have access to the context, so I need the lambda-syntax version.
I am looking for a resultant object hierarchy that looks like:
Parent1
|
+-- ChildrenOfParent1
|
Parent2
|
+-- ChildrenOfParent2
and so forth. The list would be end up being an IEnumerable. If one iterated over that list, they could get the .Children property of each parent in that list.
Ideally (and I'm dreaming here, I think), is that the overall size of the result list could be limited. For example, if there are three parents, each with 10 children, for a total of 33 (30 children + 3 parents) entities, I could limit the total list to some arbitrary value, say 13, and in this case that would limit the result set to the first parent, with all its children, and the second parent, with only one of its children (13 total entities). I'm guessing all of this would have to be done manually in code, which is disappointing because it can be done quite easily in SQL.
when you get a query from db using entityframewrok to fetch parents, parent's fields are fetched in single query. now you have a result set like this:
var parentsQuery = db.Parents.ToList();
then, if you have a foreign key on parent, entityframework creates a navigation property on parent to access to corresponding entity (for example Child table).
in this case, when you use this navigation property from parent entities which already have been fetched, to get childs, entityframework creates another connection to sql server per parent.
for example if count of parentsQueryis 15, by following query entityframework creates 15 another connection, and get 15 another query:
var Childs = parentsQuery.SelectMany(u => u.NavigationProperty_Childs).ToList();
in these cases you can use include to prevent extra connections to fetch all childs with its parent, when you are trying to get parents in single query, like this:
var ParentIncludeChildsQuery = db.Parents.Include("Childs").ToList();
then by following Query, entityframework doesn't create any connection and doesn't get any query again :
var Childs = ParentIncludeChildsQuery.SelectMany(u => u.NavigationProperty_Childs).ToList();
but, you can't create any condition and constraint using include, you can check any constraint or conditions after include using Where, Join, Contains and so forth, like this:
var Childs = ParentIncludeChildsQuery.SelectMany(u => u.NavigationProperty_Childs
.Where(t => t.child_field1 = some_appropriate_value)).ToList();
but by this query, all child have been fetched from database before
the better way to acheieve equivalent sql query is :
var query = parent.Join(child,
p => p.ID
c => c.ParentID
(p, c) => new { Parent = p, Child = c })
.Where(u => u.Child.child_field1 == some_appropriate_value)
.OrderBy(u => u.Parent.parent_field1)
.ThenBy(u => u.Child.child_field2)
.ToList();
according to your comment, this is what you want:
var query = parent.Join(child,
p => p.ID,
c => c.ParentID,
(p, c) => new { Parent = p, Child = c })
.Where(u => u.Child.child_field1 == some_appropriate_value)
.GroupBy(u => u.Parent)
.Select(u => new {
Parent = u.Key,
Childs = u.OrderBy(t => t.Child.child_field2).AsEnumerable()
})
.OrderBy(u => u.Parent.parent_field1)
.ToList();

How to use Magentor gem

I am trying to use the Magentor gem. The documentation is very weak. I succeeded in calling Magento::Category.info(1).
But I failed to call Magento::Category.create(args).
The method definition is like the following.
# catalog_category.create
# Create new category and return its id.
#
# Return: int
#
# Arguments:
#
# int $parentId - ID of parent category
# array $categoryData - category data ( array(’attribute_code’⇒‘attribute_value’ )
# mixed $storeView - store view ID or code (optional)
def create(attributes)
id = commit("create", attributes)
record = new(attributes)
record.id = id
record
end
Here's what I tried.(parent id is 1)
args = [1, {:name => 'Cars', :description => 'Great Cars', :is_active => '1', :url_key => 'cars'}]
category_id = Magento::Category.create(args)
exception: 1 -> SQLSTATE[21000]: Cardinality violation: 1241 Operand should contain 1 column(s)
Can anybody provide an example of calling the method?
I contacted the gem developer and got the following reply. A nice guy.
Hi Sam,
Sorry about the sparse documentation. We had created this library very quickly and only used a small subset of the api in the project we were working on.
It looks like the call for create in the library does not pass through data correctly. Here is a workaround:
parent_id = 1
attributes = {
:url_key=>"cars",
:description=>"Great Cars",
:name=>"Cars",
:is_active=>"1",
:available_sort_by => "Name",
:default_sort_by => "Name",
:include_in_menu => '1'
}
category_id = Magento::Category.commit("create", parent_id, attributes)
I'll also commit a fix to github that takes the parent_id correctly.
Thanks,
-preston

Resources