Hi, I am following the Writing a Blog Engine in Phoenix tutorial on hackernoon and trying to adopt it to a travel blog, and I am a complete newb to functional programming.
I have created the typical User and Post models, and added a Place model. A travel blog post is about a particular place, so the schema for Post looks like:
schema "posts" do
...
belongs_to :user, PxBlog.User
belongs_to :user, PxBlog.Place
end
And User schema is:
schema “users” do
…
has_many :posts, PxBlog.Post
end
And Place schema is:
schema “places” do
…
has_many :posts, PxBlog.Post
end
In the post_controller, and new action I had built an association between User and Post, but I do not understand how to adopt it to build an assoc to the Place model in one pipe:
Though I think it makes more sense to do that in the create action rather than new - because the relationship is not altered by the user in the new form.
The |> operator just takes the value on the left-hand side and add it as the first argument on the function on right-hand side. So in this case, conn.assigns[:user] |> build_assoc(:posts) is the same as build_assoc(conn.assigns[:user], :posts).
Because conn.assigns[:place] isn’t even a function - it’s a struct (also you probably wouldn’t store a %Place{} in the assigns map. You would get the place_id from the route params, for example).
Hi, thanks for taking the time to explain, especially the |> operator. That makes complete sense now when you put it like that.
You are right about putting this in the change action, but, unfortunately, am now stuck with the next issue, as place_id which is available from route params, post_params["place_id"], is now of type value and I get a type error when trying to make a Repo.insert with this changeset.
There is also something I can’t seem to get my head around in your suggestion:
changeset =
conn.assigns[:user]
|> build_assoc(:posts, place_id: place_id)
|> Post.changeset()
Indeed, why do we have conn.assigns[:user] |> build_assoc(:posts, place_id: placed_id) and not the other way around, i.e. conn.assigns[:place] |> build_assoc(:posts, user_id: user_id), as Postbelongs_to both User and Place?
conn.assigns is a map that will hold any information you put on it. Normally, you put the logged in user struct under the key “user” inside this map so you can retrieve it as conn.assigns[:user]. So if you don’t add a “place” key inside this map you wont be able to access it as conn.assigns[:place]. If you are adding the place key inside the conn.assigns map, it must be a struct because the build_assoc receives as fist parameter a struct.
If you do have a struct in the conn.assigns[:place] I assume it should work either way. Basically what build_assoc does under the hood is to add the id of the given struct to the association.
This is a snippet of the ecto build_assoc/3 official documentation:
Another function in `Ecto` is `build_assoc/3`, which allows
someone to build an associated struct with the proper fields:
Repo.transaction fn ->
post = Repo.insert!(%Post{title: "Hello", body: "world"})
# Build a comment from post
comment = Ecto.build_assoc(post, :comments, body: "Excellent!")
Repo.insert!(comment)
end
In the example above, `Ecto.build_assoc/3` is equivalent to:
%Comment{post_id: post.id, body: "Excellent!"}