mbrg

mbrg

Using a `no_attributes` relationship in a calculation

Hi,

I have recently finished the Ash Framework book ,and would like to add some more features to my Tunez app, starting with a friend system.

What I came up with was to model everything with a FriendRequest resource between a source and target user, like in this blog post. A user has (accepted) friend requests, a user Alice is friends with Bob if Alice’s ID appears in Bob’s list of accepted friend requests as either the target or the source.

So far I have been able to set up friendships between users and to list their friends. What I want to do now is to add an is_my_friend calculation which works similarly to the followed_by_me calculation on Artist.

My resources look like this (just the relevant bits, the Tunez resource also has account registration etc.):

defmodule FriendshipTest.Accounts.User do
  # ... snip
  attributes do
    uuid_primary_key :id

    attribute :username, :ci_string do
      allow_nil? false
      public? true
    end
  end

  relationships do
    has_many :friends, FriendshipTest.Accounts.User do
      no_attributes? true
      public? true

      filter expr(
               parent(accepted_friend_requests.target_user_id) in [id, parent(id)] &&
                 parent(accepted_friend_requests.source_user_id) in [id, parent(id)]
             )
    end

    has_many :accepted_friend_requests, FriendshipTest.Accounts.FriendRequest do
      public? true
      no_attributes? true

      filter expr(
               parent(id) in [source_user_id, target_user_id] &&
                 not pending
             )
    end
  end

  calculations do
    calculate :is_my_friend, :boolean, expr(exists(friends, id == ^actor(:id))) do
      public? true
    end
  end
  # ... snip
end

defmodule FriendshipTest.Accounts.FriendRequest do
  # ... snip
  attributes do
    create_timestamp :inserted_at

    attribute :accepted_at, :utc_datetime_usec do
      public? true
    end
  end

  relationships do
    belongs_to :source_user, FriendshipTest.Accounts.User do
      public? true
      allow_nil? false
      primary_key? true
    end

    belongs_to :target_user, FriendshipTest.Accounts.User do
      public? true
      allow_nil? false
      primary_key? true
    end
  end

  calculations do
    calculate :pending, :boolean, expr(is_nil(accepted_at))
  end
  # ...snip
end

This works for befriending users and listing their friends:

iex(1)> [user_a, user_b] = Friendshiptest.Repo.all(FriendshipTest.Accounts.User)
iex(2)> FriendshipTest.Accounts.befriend_user!(user_a, actor: user_b)
iex(3)> FriendshipTest.Accounts.befriend_user!(user_b, actor: user_a)

iex(4)> Ash.load!(user_a, :friends)
# ... database query
%FriendshipTest.Accounts.User{
  id: "48a91eeb-72ab-4161-af63-68742739c58f",
  username: #Ash.CiString<"user_a">,
  is_my_friend: #Ash.NotLoaded<:calculation, field: :is_my_friend>,
  friends: [
    %FriendshipTest.Accounts.User{
      id: "8a5a1841-9c3d-4195-8877-f64c0826f7c7",
      username: #Ash.CiString<"user_b">,
      is_my_friend: #Ash.NotLoaded<:calculation, field: :is_my_friend>,
      friends: #Ash.NotLoaded<:relationship, field: :friends>,
      accepted_friend_requests: #Ash.NotLoaded<:relationship, field: :accepted_friend_requests>,
      __meta__: #Ecto.Schema.Metadata<:loaded, "public", "users">
    }
  ],
  accepted_friend_requests: #Ash.NotLoaded<:relationship, field: :accepted_friend_requests>,
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">
}

iex(5)> Ash.load!(user_b, :friends)
#...
%FriendshipTest.Accounts.User{
  id: "8a5a1841-9c3d-4195-8877-f64c0826f7c7",
  username: #Ash.CiString<"user_b">,
  is_my_friend: #Ash.NotLoaded<:calculation, field: :is_my_friend>,
  friends: [
    %FriendshipTest.Accounts.User{
      id: "48a91eeb-72ab-4161-af63-68742739c58f",
      username: #Ash.CiString<"user_a">,
      is_my_friend: #Ash.NotLoaded<:calculation, field: :is_my_friend>,
      friends: #Ash.NotLoaded<:relationship, field: :friends>,
      accepted_friend_requests: #Ash.NotLoaded<:relationship, field: :accepted_friend_requests>,
      __meta__: #Ecto.Schema.Metadata<:loaded, "public", "users">
    }
  ],
  accepted_friend_requests: #Ash.NotLoaded<:relationship, field: :accepted_friend_requests>,
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">
}

But when I try to load is_my_friend I get this error:

iex(6)> Ash.load!(user_a, :is_my_friend, actor: user_b)
** (Ash.Error.Unknown)
Bread Crumbs:
  > Exception raised in: FriendshipTest.Accounts.User.read

Unknown Error

* ** (RuntimeError) Error while building parent reference: accepted_friend_requests.target_user_id

Query so far:

#Ecto.Query<from u0 in FriendshipTest.Accounts.User, as: 1, select: struct(u0, [:id, :username])>

Current bindings:

%{0 => %{type: :root, path: [], source: FriendshipTest.Accounts.User}}

  (ash_sql 0.2.86) lib/expr.ex:3302: AshSql.Expr.reference_error!/2
  (ash_sql 0.2.86) lib/expr.ex:2283: AshSql.Expr.default_dynamic_expr/6
  (ash_sql 0.2.86) lib/expr.ex:1851: AshSql.Expr.default_dynamic_expr/6
  (ash_sql 0.2.86) lib/expr.ex:1201: AshSql.Expr.default_dynamic_expr/6
  (ash_sql 0.2.86) lib/expr.ex:1851: AshSql.Expr.default_dynamic_expr/6
  (ash_sql 0.2.86) lib/expr.ex:1201: AshSql.Expr.default_dynamic_expr/6
  (ash_sql 0.2.86) lib/filter.ex:38: anonymous fn/2 in AshSql.Filter.add_filter_expression/2
  (elixir 1.18.4) lib/enum.ex:2546: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ash_sql 0.2.86) lib/expr.ex:3302: AshSql.Expr.reference_error!/2
    (ash_sql 0.2.86) lib/expr.ex:2283: AshSql.Expr.default_dynamic_expr/6
    (ash_sql 0.2.86) lib/expr.ex:1851: AshSql.Expr.default_dynamic_expr/6
    (ash_sql 0.2.86) lib/expr.ex:1201: AshSql.Expr.default_dynamic_expr/6
    (ash_sql 0.2.86) lib/expr.ex:1851: AshSql.Expr.default_dynamic_expr/6
    (ash_sql 0.2.86) lib/expr.ex:1201: AshSql.Expr.default_dynamic_expr/6
    (ash_sql 0.2.86) lib/filter.ex:38: anonymous fn/2 in AshSql.Filter.add_filter_expression/2
    iex:6: (file)

Am I missing something obvious?

I have also put the code on GitHub, along with a failing testcase if that helps.

Most Liked

mbrg

mbrg

https://github.com/ash-project/ash/issues/2214

Thanks for the quick reply, lease let me know if I need to change anything.

Where Next?

Popular in Questions Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: &lt;h1&gt;Create Post&lt;/h1&gt; &lt;%= ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: The documentation above suggests that while ...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New

Other popular topics Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42920 311
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
New

We're in Beta

About us Mission Statement