Phoenix v1.3.0-rc.0 released

perhaps you somehow looked at outdated code, but we have Hexpm.Web.Plugs defined in lib/hexpm/web/plugs.ex [1] and so the module name perfectly matches the filename.

[1] https://github.com/hexpm/hexpm/blob/master/lib/hexpm/web/plugs.ex#L1

1 Like

@chrismccord What do you think about this idea?

1 Like

Yes, that is exactly what I pointed out. What makes Plugs privileged so it doesnā€™t follow this new, odd structure (base namespace module defined in the folder for the namespace)? plugs.ex should, according to this new idea, be placed in plugs/.

I was pointing out that itā€™s exactly where it should be, but not where this new convention says it should be.

This is assuming I havenā€™t misunderstood the new convention to only apply to lib/* directories, in which case itā€™s actually even worse, as itā€™s not even internally consistent.

1 Like

OK, I see what you mean, I didnā€™t make the connection that plugs.ex could indeed be in lib/hexpm/web/plugs/, thatā€™s a good point. It does make sense to me to move it there, Iā€™d also like to give this module a more meaningful name, but nothing pops up yet.

I was pointing out that itā€™s exactly where it should be, but not where this new convention says it should be.

As was mentioned in this topic a couple of times (e.g. [1] [2]), Elixir doesnā€™t care where you place your files. If you donā€™t like the new structure nobody forces you to use it. Iā€™d say, follow this convention as much or as little as you want.

To give a concrete example from this refactoring effort, I particularly like how e.g. Hexpm.CDN* [3] modules are currently placed. Note this context is a bit different from e.g. Accounts context (which uses Repo, Schemas etc), but similar in a way that it represents a slice of the application. This set of modules is totally independent of Hex.pm, it could easily be a separate package, and so I quite like that all files are in the same directory. Does it break conventions in file structures compared to projects like Ecto, Poison etc? Yes. Does it make it easier for me to navigate Hex.pm project? Yes!

[1] Phoenix v1.3.0-rc.0 released - #44 by josevalim
[2] Phoenix v1.3.0-rc.0 released - #45 by rafal-radziszewski
[3] hexpm/lib/hexpm/cdn at main Ā· hexpm/hexpm Ā· GitHub

3 Likes

I see your point and Iā€™ll have it in mind. I feel awkward about it now, though, because itā€™ll still feel like Iā€™m now going against convention. The community is likely to adapt one or the other and I usually need good reasons to break convention.

Anyway, thanks for the rationale and example. Itā€™s something to keep in mind.

2 Likes

@chrismccord the way generators now put the changeset functions into context file and not in the schema itself breaks the contract expected by Ectoā€™s cast_assoc. Since cast_assoc is almost always expected in any most changeset, having the changesets in context file requires users to define additional changesets in schema file just for the association or use the with option in cast_assoc. I think moving the changeset functions to schema can reduce the noise in context file and at the same time relive developers from defining changesets for associations.

2 Likes

Thatā€™s a very good point we have not considered before. Can you please open up an issue so we are sure to discuss it?

1 Like

To be honest, I personally consider cast_assoc defaulting to Schema.changeset/2 a mistake - the with option should be obligatory.

4 Likes

Opened an issue here: https://github.com/phoenixframework/phoenix/issues/2180

1 Like

Iā€™m interested in this topic as Iā€™m developing a Phoenix 1.3 application as a learning tool to get comfortable with the conventions around contexts and API boundaries. Something Iā€™ve noticed is that embedded schemas donā€™t quite fit into the pattern of code thatā€™s generated by Phoenix. For example, create_<object> doesnā€™t necessarily make sense for an embedded changeset (say, to validate fields for a login form).

This is also related to the topic in https://elixirforum.com/t/clarity-needed-on-phoenix-1-3-contexts-and-schema-in-controllers/4097/7. Since itā€™s often required to generate an empty changeset, it would be good to have a function for that purpose automatically generated on the context module. @shankardevy suggests build_<object>/1, which I find sensible.

Furthermore, I would agree that the private functions to generate changesets (<object>_changeset/2) may be better positioned on the schemas themselves ā€“ as this is most likely where developers will be implementing things like required fields, validation etc.

Overall, though, I think the changes are really great - so thank you for all your hard work!

2 Likes

Until we can implement strong AI (Phoenix 4.0? :-P), we wonā€™t be able to determine the perfect name for your functions. I agree that crud functions names arenā€™t always what you want, and I definitely recommend picking other function names that better describe your intent; however, you very well can model a login form from an embedded schema, then persist some other table-backed schema to the DB after validation. In that case, create_user still works, but I would prefer something like Accounts.register_user. create/update/delete/list/get are starting points, but by all means use naming that better describes the intent of your features.

Iā€™m still unsure on this one since change_<type> already serves the purpose of generating an empty changeset. The only way build_whatever would make sense is if it returned a %Whatever{} struct, in which case it wouldnā€™t be necessary.

Thanks! Glad to hear it!

3 Likes

Iā€™ve been using the new phx.gen generators and I came across a use case that left me wondering if maybe I am not grokking the new context based directory layout correctly.

I want to have a User schema in two contexts, Account and Blog, so I run:

mix phx.gen.html Accounts User users email:string
mix phx.gen.html Blog User users name:string account_id:references:accounts_users

The second generator invocation tries to overwrite the web_app resource files created by the first generator invocation:

* creating lib/phx_sandbox/web/controllers/user_controller.ex
lib/phx_sandbox/web/controllers/user_controller.ex already exists, overwrite? [Yn] 

Is that intentional?

Should web_app resources also be name spaced, according to the context? (I must admit, that feels odd)

Or should there be a clean separation between context schemas and web_app resources, so that the above generator invocation should not be allowed, requiring instead explicitly linking a resource to a context schema?

mix phx.gen.html Account User users email:string
# => Raises exception suggesting you do the following instead

mix phx.gen.context Account User users email:string
mix phx.gen.context Blog User users name:string account_id:references:accounts_users

# Creates CRUD scaffold linked to each contexts User
mix phx.gen.html Account User SignupController
mix phx.gen.html Blog User BlogUserController
4 Likes

How do we associate different contexts like say user has a account( Account context) and has many orders( Order context).

2 Likes

@wojtekmach I was wondering what your thoughts are regarding ā€œnot respectingā€ the context boundary as explained on some of the replies above? For example, I see that in hexpm (https://github.com/hexpm/hexpm49) you donā€™t have an accounts.ex file and the controllers reach directly into modules within the context boundary (e.g. UserController reaches into Hexpm.Accounts.User which crosses the ā€œAccountsā€ context boundary). It seems to me that the phoenix core team is trying to encourage the community to ā€œrespect the context boundariesā€ but looking at hexpm Iā€™m wondering if Iā€™m missing something. Any thoughts you can share with me on this topic would be very appreciated. :slight_smile:

1 Like

@sabondano Iā€™m very much in favor of creating and keeping boundaries in my code. To respond to your particular example, I believe youā€™re referring to line: public_email = User.email(user, :public) in the controller [1]. If so, to be honest, Iā€™m not too concerned about it because if you look into implementation of User.email/2 [2] youā€™d see it operates on user.emails field on the struct. I donā€™t want to draw the line whatā€™s right and whatā€™s wrong and itā€™s something Iā€™m still developing an intuition on. However, what Iā€™m sure is Iā€™d be much more concerned if the controller would directly operate on Repo or Queries regarding the User schema.

To give more context (or to make excuses :)) weā€™ve introduced context modules several months before weā€™ve made the switch to Phoenix v1.3 structure and itā€™s still definitely rough around the edges. I think thereā€™s still room for improvement and itā€™s something I want to pursue.

[1] https://github.com/hexpm/hexpm/blob/0ce8169739d9baf061d6be5ba9e4940a7d0515b6/lib/hexpm/web/controllers/user_controller.ex#L11
[2] https://github.com/hexpm/hexpm/blob/0ce8169739d9baf061d6be5ba9e4940a7d0515b6/lib/hexpm/accounts/user.ex#L103

2 Likes

With the new configuration system:

on_init: {MyApp.Web.Endpoint, :load_from_system_env, []},

It seems we are forced to use IPv6 (https://github.com/phoenixframework/phoenix/blob/438b74255e7a4d68b4eaf1a295d0fcd201c71421/installer/templates/phx_web/endpoint.ex#L52).

If the old way to do config is deprecated (https://github.com/phoenixframework/phoenix/blob/da9f7653b9daf29a4c415be52a19ee6f4473e083/lib/phoenix/endpoint/handler.ex#L54), how should I configure the port to work if I donā€™t have IPv6?

You can remove :inet6 from the config. Change this:

{:ok, Keyword.put(config, :http, [:inet6, port: port])}

to this:

{:ok, Keyword.put(config, :http, [port: port])}

Oh, I didnā€™t pay attention. I didnā€™t see it was actually a template used to generate a file in my project.

Thank you!