Standard String representation for entities - what is the Elixir way of replacing Ruby's to_s() method?

Hi,

I’m currently learning Phoenix. In my Rails apps, I always define a to_s() method on each and everyone of my ActiveRecord classes. That allows me to build strings anywhere in my apps that look like "The entity #{my_entity_var} has been processed successfully" that are automatically rendered as “the entity Output Of to_s() Method has been processed successfully”.

Is there a standard Phoenix or Elixir way of defining a custom string representation for Ecto entities or, more generally, for Structs ? What is the “best practices” way of doing something like that ?

Thanks in advance.

1 Like

It sounds like you want to implement the String.Chars protocol: String.Chars — Elixir v1.13.1

Here’s some info on protocols: Protocols - The Elixir programming language

5 Likes

Doesn’t this mix data layer and presentation concerns though? I think the idiomatic Elixir pattern would be to define a function that took an Ecto Struct and returned the desired string representation for the situation. So eg:

"Entity #{display_type(entity)} updated successfully"

where display_type is a view layer function that transforms arbitrary structs to strings. The String.Chars protocol is generally designed for datastructures that themselves are basically analogous to text, and gives a generic way to do a canonical transform to that string. For an ecto struct, its textual representation varies by use case heavily.

2 Likes

Yay! I love it! Thank you so much!
Where would be a good place in a Phoenix application to define the implementation of the String.Chars protocol for my Ecto entities ? In my first trial I added a defimpl to an Ecto schema file, how does that feel ? Would it be better to rather have all of my defimpl definitions in one file per protocol ? Comming from an oo world I’m leaning towards “one place to define entities behaviour” way (-> in the schema). What would be the Elixir way?

That’s interesting. Let’s say that I would like to normalize entity display in my app for standard cases. What would you think of a specific protocol that I would implement for all my entities and that would have several functions depending on context, e.g. display_type1, display_type2 ?

I definitely want to be able to consider most of my entities as analogous to text. Having standard representations has always been a huge help in all of my big apps, even if (obviously) it doesn’t work in all cases. Usually, the standard representation is enough for most entities, and I write specific code for the others.

This is fine in small apps you own yourself, but as @benwilson512 suggests, this is absolutely mixing data and presentation, even in your Ruby example.

The problem is is that it creates two ways of doing one thing and spreads them across two different layers. Anyone coming into your app would have to be aware of this. Then, there would have to be rules around what is considered the “standard” representation that goes in the model and what goes in the view helpers. And then what happens if one of the the view helpers starts getting used more often than the model version? Does that mean everything needs to be refactored to move stuff around? It’s far easier to just keep presentation logic at the view layer and leave the model alone. While I agree this isn’t the most complex thing in the world, I find that any opportunity to simplify even the smallest of things is a big win. Avoid death by a thousand cuts—and avoid bike shedding with your colleagues!

Anyway, that’s the reasoning, but like anything in programming, it’s just a suggestion. A friend of mine uses #to_s to good effect in small cli tools he builds in Ruby where there only ever ends up being one, and only one, way to present each object. But I wouldn’t suggest it for even a medium-sized web app that more than one person will be working on.

1 Like

I would challenge that by asking if they are going to have a parse function that does the opposite? If so, then go for it. If not, I think you’re looking for something like label(entity) as was mentioned already.

1 Like

The discussion is starting to drift away from the original point, but that’s interesting to see the Elixir and functional orientation at work here. I like the explicitness and separation of concern that you suggest, but I also like the “convention over configuration” way that I use in Ruby. It gives me conciseness and semantics and I like having as much semantics as possible in my code. It is not easy to find the good balance between code that is semantically easy to understand and technically easy to follow. I see that Elixir people tend to push more towards the “technically easy to follow” side.

Moreover there are lots of cases where the separation of concerns between model and view isn’t so clear cut as that. Validation messages for example, where do they belong ? They are part of my model invariants, but they are also viewable messages for my users. They may as well be read by my local users via a basic crud app, but they might also travel far away through several API layers to external apps that I don’t control. In such cases, I like having a standard textual semantic representation of my entities. I don’t always control where they will be seen, but I know that it will always be via a human brain and human eyes.

1 Like

That’s interesting. When thinking about it, they might in some of the cases. Probably not all of them.

Let me ask you a question : how would you manage a validation message (let’s say in an Ecto changeset) that says something like “id number #{entity.id_number} is not valid for entity #{entity}” ?

The generated default phoenix code is a good start. The ecto schema returns structured data as errors which, at the front end, you pass through a view helper that handles translation if necessary, and normalizes that datastructure to front end oriented strings.

The resulting string should probably read something like: “ID 123 is not a valid Location” or something like this, I’m not entirely sure where the actual #{entity} bit would occur honestly.

Not arguing for the sake of arguing, but trying to give you a different perspective: what you return in the API responses is a presentation layer concern too. In a lot of cases, returning your DB rows as JSON is going to work just fine, but you don’t want to paint yourself into a corner - many people, including myself, got bitten by this when it turned out that the JSON schema needs to differ from the DB design, or maybe you need two separate JSON schemas for different endpoints or APIs. The way out of that is to treat JSON API as a view layer for your data and build actual views to render that data.

When you think of it that way, those validation messages are again part of the presentation layer.

Obviously, you can (and probably sometimes should) take shortcuts depending on your needs, but there are strong reasons for keeping things separate. This is what the Phoenix guides are suggesting.

1 Like

The #{entity} part is the essence off my question : I want semantic validation messages that talk to humans about the specific entity that is being validated. It could be “… for color Red”, “for phone number +123456789”, “for user John Smith”, “for line 123-abc-234”, etc.

Got it. And I think this gets to the crux of the matter: In general the Elixir community thinks that it is very important to be explicit about such things. How to refer to a user varies by context. Sometimes name works, sometimes it’s employee ID, sometimes it’s username. A shipment is referred to by its order number in some cases and it’s tracking number in others.

Defaults like String.Chars create easy pathways, and people tend to default to the pathways that are easiest. As your program grows, a single canonical way won’t suffice so you’ll have several variations anyway eg: #{username(user)} vs #{short_name(user}) vs #{user} and in that last case it requires everyone to simply remember “oh right, the default is long name”. Instead it could just be #{full_name(user)} like the rest and then the code just tells you what it’s doing.

4 Likes

That, but also if this should indeed be a global concept I’d still use a protocol, but your own instead of String.Chars. Somethings like MyApp.HumanizedIdentityString.to_string/1. That can be used in your presentation layer (like described before) to replace placeholders in your validation errors.

6 Likes

It is my first question here and the quality of the discussion and of the answers is staggering! Thanks to all those who answered!

5 Likes