Morzaram

Morzaram

Hey everyone,

I have a live view app that is slowly getting bigger and I’m really struggling to find a way to manage all of the logic for each page. Components, queries, page, etc.

I was hoping to get some insight on how I can best have file structure and other tips on making sure I can manage all the code for these things. Right now I feel that the contexts are getting in the way and I should be moving the queries in a file next to the page view itself.

Any insight and resources would be much appreciated!

Showing Posts 1 to 10

cmo

cmo

I put components relevant to only the one page in a folder with or under the page. If there is a lot of web domain logic, create a AppWeb.Whatever that houses all of that. The extra queries I leave over in App.Whatever or create a module under that or a new one if there is a lot of logic for a subset of the context.

I feel like putting your queries in the web domain is crossing a line that a lot of people (but not all) are uncomfortable with.

Morzaram

Morzaram OP

Thanks for responding! Is there a clearer example you can give me?

For example: I have a page MyAppWeb.Admins.Locations.Events.Index that has a list of events that you can:

  • filter by location
  • filter by category
  • move to the next month
  • move to the previous month
  • show/hide the events that already have occured this month
  • filter by the person who created the event

I have quite a few pages like this and its starting to feel a little overwhelming.

The more that I add here the less it feels that the query is belonging on the Web domain side and that background job logic belong on the MyApp side.

They feel too far away from each other at this point both because it’s in a 2x nested resource and it’s a lot of logic to handle.

Therefore I’m thinking MyAppWeb.Admins.Locations.Events.Index.Queries feels a bit better but I’m not sure if this the way to go.

kokolegorille

kokolegorille

Instead of passing queries from web… can You pass criteria to the queries? I use the liveview to manage parameters I send to the query

I have one list_events function that do this only by passing parameters to the query. Do You have multiple queries instead?

Morzaram

Morzaram OP

Thanks for also responding!

So you’re suggesting have a large query struct and then add conditionals based on what is in the query struct? Thus basically a god query for Events?

kokolegorille

kokolegorille

I am suggesting a technique I learned from the book Absinthe/GraphQL..

For example…

  def list_requests_query(criteria \\ []) do
    query = from(p in Request)

    Enum.reduce(criteria, query, fn
      {:limit, limit}, query ->
        from p in query, limit: ^limit

      {:offset, offset}, query ->
        from p in query, offset: ^offset

      {:filter, filters}, query ->
        filter_with(filters, query)

      {:order, order}, query ->
        from p in query, order_by: [{^order, ^@order_field}]

      {:preload, preloads}, query ->
        from p in query, preload: ^preloads

      arg, query ->
        Logger.info("args is not matched in query #{inspect(arg)}")
        query
    end)
  end

  defp filter_with(filters, query) do
    Enum.reduce(filters, query, fn
      {:user_id, user_id}, query ->
        from q in query, where: q.user_id == ^user_id

      {:is_fetched, is_fetched}, query ->
        from q in query, where: q.is_fetched == ^is_fetched

      {:is_post_processed, is_post_processed}, query ->
        from q in query, where: q.is_post_processed == ^is_post_processed

      {:with_medium_path, true}, query ->
        from q in query, where: not is_nil(q.medium_path)

      {:with_medium_path, false}, query ->
        from q in query, where: is_nil(q.medium_path)

      arg, query ->
        Logger.info("args is not matched in query #{inspect(arg)}")
        query
    end)
  end

  def list_requests(criteria \\ []) do
    criteria
    |> list_requests_query()
    |> Repo.all()
  end

Using this, I could probably do something like…

        filter by location
        filter by category
        move to the next month
        move to the previous month
        show/hide the events that already have occured this month
        filter by the person who created the event

… and this would translate to

[filter: [by_location: location, by_category: category, by_month: month ...]]
|> App.list_requests()
17
Post #5
Morzaram

Morzaram OP

Wow this is beautiful! Thank you for this. I think this is exactly what I need

cmo

cmo

Sometimes it’s nice to have something a bit more high level than passing a keyword list of ecto query options, which can be in addition to or instead of the more low level method in the post above. Maybe you specify options filter: filter, show_already_occured: true, after: last_event_on_page in the web context and convert that to the lower level options in your app domain.

If you feel there is too much query logic in the Events module, you can run a separate EventQuery module with composable queries and functions in your Events module will pipe through them. For example, you can have a EventQuery.with_filter function that takes your filter (that is serialisable to URL query params) and turns it into ecto query options. In doing that, you probably don’t need to split Events.Index and can flatten into Events

jmagnani

jmagnani

Is it possible to follow a vertical-slice architecture in Phoenix or LiveView, to organize code?

cmo

cmo

Phoenix doesn’t impose any major constraints on how you organise your files.

mikesax

mikesax

I’m (much) behind you on this journey but here are a few things things I’ve found helpful:

  1. Elixir really doesn’t care where in your modules are located in your project, so I haven’t been shy about grouping things that I think belong together in their own subfolder, or moving things around.
  2. I occasionally reorganize the “namespaces” (they’re not really namespaces, but I mean the names in between dots of a full module name) and move functions around between modules. It seems to be much easier in Elixir than in an OO language/system, and Elixir language server does a truly great job telling you what needs fixing. I hope that I’ll need to do less of this as I get more experienced with Phoenix, but it’s nice not having to make the right calls the first time.
  3. I haven’t used defdelegate much yet, but that seems like a great way to isolate code that takes on a life of its own and only expose what you need in your main api modules.
  4. Adopting Elixir by Ben Marx, José Valim, and Bruce Tate is a great book that seems like it was written to answer your exact question!

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews