Ecto has_one based on type

Hello!
I am working on partial migration of an existing app to elixir. DB structure is already exists and couldn’t be easily changed.
I have next situation:
table ‘components’ has all available components in system and has field type. Types are hardcoded: html, twitter_feed, rss etc. So it’s something like a widgets.
Data for each type is saved in separated table: components_html, components_twitter_feed, components_rss etc… And each of such table has relation with main component record with a field ‘component_id’
Schema is implemented in Component/ComponentRss/ComponentHtml etc
I have has_one relation in Component for each type:
has_one :html, ComponentHtml
has_one :rss, ComponentRss

and belongs_to in child schema:
belongs_to :component, Component

Let’s say I want to access route ‘components/:id’ and return JSON in format:

{
  id: 123,
  type: 'html',
  name: 'Html Test',
  pivot: { 
     // All data from table components_html
    id: 1,
    component_id: 123,
    html_content: '<h1>It"s html content'
 }
}

For another component it could be another pivot data from another table (dependent on type):

{
  id: 124,
  type: 'rss',
  name: 'Rss feed',
  pivot: { 
     // All data from table components_rss
    id: 1,
    component_id: 124,
    feed_url: 'https://feed.url'
 }
}

I stuck a bit how to implement reading based on type.
I can’t do it like this

Component
    |> Repo.get!(id)
    |> Repo.preload(:html)
    |> Repo.preload(:rss)

There 15 types, and with that approach it will read from all tables (but only one contains valid record).
Plus I need to make a json. Do I need to create view response function for each type of component using matching patterns?
I can’t figure out how to make some multipurpose solution to have:

%{
      id: component.id,
      type: component.type,
      pivot: %{
        component_rss_structure (json representation of ComponentRss/ComponentHtml/etc)
      }
    }

Later I will call PUT route to update record from similar structure. I need somehow to update sub-component data from ‘pivot’ structure in request. How can I dynamically choose sub-component?

Thanks in advance.

I stuck a bit how to implement reading based on type.
I can’t do it like this

Component
    |> Repo.get!(id)
    |> Repo.preload(:html)
    |> Repo.preload(:rss)

but you can do this:

component = Repo.get!(Component, id)
component = Repo.preload(component, component.type)

if type doesn’t match assoc (not an atom or different naming), create a helper function type_to_assoc_name/1 that will do this mapping.

1 Like

Thank you! yep, I did something like this. There is still a lot of places to improve, but it’s working already:

def get_component!(id) do
    Component
    |> Repo.get!(id)
    |> preloads()
  end

  defp preloads(component) do
    component
    |> Repo.preload(Component.get_type_as_atom(component.type))
  end

And implemented JSON for model:

def get_type_as_atom(type) do
    %{
      "html" => :html,
      "rss" => :rss
    }[type]
  end
defimpl Jason.Encoder, for: Component do
    def encode(value, opts) do
      fields = [Component.get_type_as_atom(value.type) | [:name, :type]  ]
      m = Map.take(value, fields)
      Jason.Encode.map(m, opts)
    end
  end