Hacker News new | past | comments | ask | show | jobs | submit login
Can We Have Form Objects in Elixir? (lelonek.me)
48 points by squixy on March 28, 2017 | hide | past | favorite | 17 comments



I started my application before the embedded_schema API was available and the Assumption that a RDBMS was backing everything was a major pain-point for me at the time leading me to create a custom form abstraction using Vex (https://github.com/CargoSense/vex). Now with embedded_schema the pain went away and I ended up refactoring my form modules away completely in favour of the new API.

I was using Ruby myself for 10 years before using Elixir and the Form object pattern was one I used very often myself. A general principle of Elixir I have often seen that if you think something doesn't work, or is the wrong way you in fact need to unlearn some of the brain damage acquired from Ruby.

A great example of this is Wojtek Mach's github_ecto project (https://github.com/wojtekmach/github_ecto). His abstracting of the Github API behind a Ecto.Repo was mindblowing to me. Your app communicates with a HTTP API without knowing it's any different than your DB.

Elixir is very much a different way of thinking about problems than Ruby.


Holy crap. That's amazing. Thanks for sharing. Been digging into Elixir off and on for months, and hadn't come across this. Mind-blowing, indeed.


For what it's worth, I've been learning Erlang and this was my first shot at json form parsing:

https://github.com/cieplak/chat/blob/master/src/http/forms.e...

https://github.com/cieplak/chat/blob/master/src/domain/types...

No data validation at the moment, but it would be nice to generate the form parsing and validation, as well as the getters and setters, from a declarative spec.


Totally of topic but could someone explain Elixirs syntax to me?

In the following code (from the linked site): What is embeds_many? Are :string and :map type information or Atoms? What happens to :changes, Change, primary_key? Does the code between do and end just call the field function twice?

embeds_many :changes, Change, primary_key: false do

    field :field, :string

    field :value, :map
end


It's confusing because it's using a library called Ecto, which provides it's own DSL for describing schemas and queries.

NOTE: I haven never used Ecto, but I have used Elixir for a few hobby projects.

1. `embeds_many` is a function name, described here: https://hexdocs.pm/ecto/Ecto.Schema.html#embeds_many/3

2. Anything that begins with a colon is an Atom in Elixir, including :string, :map, :changes, :field, and :value. These atoms are used by Ecto at runtime to identify certain things. `:changes` is a unique name given to the embedded schema, `:field` and `:value` are field names on that schema, and :string and :map are informing Ecto of how to treat certain values.

3. `Change` is the name of a Module which describes the Schema we are embedding

4. `primary_key: false` is a configuration option

5. Everything after `do...` is describing what the embedded schema would look like. So it has a `:field` field that is of type string, and a `:value` field that is of type map.

Hopefully that makes some sense... I'm not totally clear on it either (having not used Ecto before) but that's what I'm grokking.


Thanks


This code snippet is a little bit confusing if you aren't already familiar with Elixir, because most of the syntax you're seeing is actually from Ecto's [0] DSL. Calling "use Ecto.Schema" at the top of the module brings some extra functionality into the current scope. For example, embedded_schema is a macro exported by Ecto.Schema [1].

embeds_many is another such macro [2], that allows you embed another schema, in-line, into the current schema. This is contrast to your more common has_many relationship, which references another table entirely.

Here, :string, :map, etc are atoms that are being passed to the field macro [3], to define the schema for the table.

[0] Basically a super lightweight ActiveRecord

[1] https://github.com/elixir-ecto/ecto/blob/b030353d4b94ddd4216...

[2] https://github.com/elixir-ecto/ecto/blob/b030353d4b94ddd4216...

[3] https://github.com/elixir-ecto/ecto/blob/b030353d4b94ddd4216...


Thank you


Others provided an explanation of the Ecto DSL, I just wanted to add the explanation of basic syntax. It goes like this.

In Elixir, the most basic function application looks fairly standard:

    func_name(arg1, arg2, ...)
But the parens are optional, so you can write:

    func_name arg1, arg2, ...
as well.

Next, in Erlang and Elixir function arity (number of arguments it takes) is always fixed (i.e. no varargs or Python's *args). Because of this you generally have to pack your arguments in a list; functions like `printf` work that way:

    iex(2)> :io.format("fmt ~p foo: ~p bar: ~p", [1, 3, 4])
    fmt 1 foo: 3 bar: 4
That takes care of (positional) varargs. There are some other functions, however, which would benefit from "keyword arguments", which also isn't directly supported. To get around this, you can take a list of pairs (keyword, value) as one of your arguments (usually last):

     some_func(arg1, [{:keyword1, value}, {:keyword2, value}])
Where `{}` creates a tuple and `:` creates an atom (called symbols in Lisp), which you can think of as a special[1] string value. This pattern is so common, that Elixir provides a syntactic sugar for it. The above is equivalent to:

     some_func(arg1, [keyword1: value, keyword2: value])
The brackets around keyword list are also optional, but only inside function call. The most sugar-y way to write this looks like this:

     some_func arg1, keyword1: value, keyword2: value
The last thing of note is that the `do ... end` is just a syntactic sugar for `[do: ...]`, where `...` is treated as a block of code.

Normally, the block of code would be evaluated and only last value would be passed to the function:

     some_fun arg1, kwarg1: 1, kwarg2: 3 do 1; 2; 3 end   # notice no comma before `do`
would be equivalent to (after all the sugar being resolved):

     some_fun(arg1, [{:kwarg1, 1}, {:kwargs2, 2}], [{:do, 3}])
However, when writing a macro, the macro gets the unevaluated block, which then can be modified or executed. See http://elixir-lang.org/getting-started/meta/macros.html for more info on that.

Edited to add: Capitalized names in Elixir are actually special atoms, mostly used for naming modules. It works like this:

    iex(5)> :'Elixir.Change' == Change
    true
There is more going on because of the fact you can alias module names, but mostly you can think about this as (another) special syntax for atoms.

[1] Allocated once and cached. When working with normal strings:

    a = "Asd"
    b = "Asd"
you have no guarantee that `a` and `b` point to the same thing in memory, while with atoms you do have that guarantee. This makes comparing them efficient.


> But the parens are optional

In the newest Elixir I get a warning about a variable not existing when I omit the parenthesis, and it suggests to add them.


This is usually the case when there is potential for ambiguity, but there's also a matching function the non-parens version can be expanded to.



I read this and it doesn't answer my questions


It's good to see more people developing in Elixir. But having said that, I'm against server-side forms. I feel client side frameworks like vue.js completely alleviates the need for server-side form objects and does a lot more. Server-side forms at times can be a real pita to work with.


You still need server side validation so a form object with validators is still useful.


server side validation (which is indeed requried) != forms. also, Ecto does really good job with changesets, including hard-to-do-right pieces like "uniqueness"


IMHO, reusable server side validation == forms without widgets (and widgets are nice to have if one feels that JS is unnecessarily complicating what could be a simple static HTML form).

Or I'm missing something?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: