Ecto has_one without belongs_to in Phoenix - phoenix-framework

I have a Home model which contains content for a home page such as intro_copy, about_image and about_copy.
On the Home model, I’d also like to be able to feature 3 posts from my Post model using has_one relationship. Basically just linking them using an id.
My Home schema looks like this:
schema "home" do
field :intro_copy, :string
field :about_copy, :string
field :about_image, Image.Type
has_one :post_1, Post
has_one :post_2, Post
has_one :post_3, Post
timestamps()
end
My changeset function looks like this:
def changeset(struct, params \\ %{}) do
struct
|> cast_assoc(params, [:post_1, :post_2, :post_3])
|> cast(params, #required_fields, #optional_fields)
end
Also, in my migrations I have the following lines being added to the :home table:
add :post_1_id, references(:posts)
add :post_2_id, references(:posts)
add :post_3_id, references(:posts)
Is there somewhere I’m obviously going wrong here?

If the home table contains references to posts, then Home should belongs_to Post. has_one is for the reverse -- you'd use it here if posts contained a field that referenced home.
If you change:
has_one :post_1, Post
has_one :post_2, Post
has_one :post_3, Post
to
belongs_to :post_1, Post
belongs_to :post_2, Post
belongs_to :post_3, Post
everything should work with the migration you've already written.

Related

class_name foreign_key in Rails model

I recently come across this code. User has many Answer. What is the purpose of the :class_name and :foreign_key ?
class Answer < ApplicationRecord
belongs_to :user, :class_name => 'Question", :foreign_key => 'question_id'
end
The naming here is kind of strange, but the purpose of :class_name is to allow you to use a class that is different from the one Rails expects. When you have a belongs_to :user on a model, Rails would expect that to point to a parent class called User. In your example, Rails skips looking for a User class and instead looks to the Question model.
The most common usage of this, though, is when a non-default association name makes more sense than the default. So a more apt example is when you have a User model and Competition model wherein each competition has one user as a winner. You could have each Competition belong to a User, but that wouldn't be as expressive. Instead you may want to have the relationship be referred to as winner:
class User < ActiveRecord::Base
has_many :winners, class_name: "Competition", foreign_key: "competition_id"
end
class Competition < ActiveRecord::Base
belongs_to :winner, class_name: "User", foreign_key: "winner_id"
end
This allows you to refer to users as winners:
competition = Competition.first
competition.winner
This is a lot more expressive than if you were to write competition.user.

nested form objects fails validation complaining of missing parent_id

I use simple_form. When using rails accepts_nested_attributes_for, the form fails validation complaining that the nested attributes validation failed because the id of the parent object can't be blank (validates_presence_of).
I know the parent object id is being submitted, because if I remove validation the form submits, and the child record is valid. So, the validations are looking at the params before the parent id is associated with the child...and therefor failing. This seems strange. Why is rails running validation at a stage in form submission before the parent of the nested attributes is correctly associated?
Is there a rails way for handling this scenario?
Just found the answer to this in the Rails docs. Here's their example, using inverse_of:
class Member < ActiveRecord::Base
has_many :posts, inverse_of: :member
accepts_nested_attributes_for :posts
end
class Post < ActiveRecord::Base
belongs_to :member, inverse_of: :posts
validates_presence_of :member
end

DataMapper - Set repository for many to many relationship using anonymous resource

I'm trying to migrate an old database using DataMapper and I am having an issue with a many to many relation.
I have a Post and Tag model that both go through an anonymous resource. I can set the repository name in the post and tag models, but not the auto-generated PostTag model (as far as I know). Is there a way to make all of them use the same repository name (:legacy)?
Cheers,
Tom
You can just create a normal DM model for the "middle" resource to be able to define the repository name, such as
model PostTag
include DataMapper::Resource
def self.default_repository_name; :legacy end
belongs_to :post, :key => true
belongs_to :tag, :key => true
end
and in both of those parents, define the connection with a :through. For example,
model Post
# other definitions ...
has n, :post_tags
has n, :tags, :through => :post_tags
end

Rails STI - custom association in subclass

class Upload < ActiveRecord::Base
has_many :comments
end
class Gallery < Upload
has_many :images
end
class MusicAlbum < Upload
has_many :audio_tracks
end
Should this work as expected? Will Gallery and MusicAlubm models inherit :comments association from their parent (Upload) and add their own?
Yes, the models are just classes, and when inherited they get all the methods from parent class. So, as both Gallery and MusicAlbum are descendants from Upload model, they will have the has_many :comments association, and both will get data from uploads db table (which needs to have a type column to support STI for this model)
A nice simple STI example can be found here

Rails multiple belongs_to assignment

Given
User:
class User < ActiveRecord::Base
has_many :discussions
has_many :posts
end
Discussions:
class Discussion < ActiveRecord::Base
belongs_to :user
has_many :posts
end
Posts:
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
end
I am currently initializing Posts in the controller via
#post = current_user.posts.build(params[:post])
My question is, how do I set/save/edit the #post model such that the relationship between the post and the discussion is also set?
Save and edit discussions along with post
Existing Discussion
To associate the post you're building with an existing discussion, just merge the id into the post params
#post = current_user.posts.build(
params[:post].merge(
:discussion_id => existing_discussion.id
)
You will have to have a hidden input for discussion id in the form for #post so the association gets saved.
New Discussion
If you want to build a new discussion along with every post and manage its attributes via the form, use accepts_nested_attributes
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
accepts_nested_attributes_for :discussion
end
You then have to build the discussion in the controller with build_discussion after you built the post
#post.build_discussion
And in your form, you can include nested fields for discussions
form_for #post do |f|
f.fields_for :discussion do |df|
...etc
This will create a discussion along with the post. For more on nested attributes, watch this excellent railscast
Better Relations
Furthermore, you can use the :through option of the has_many association for a more consistent relational setup:
class User < ActiveRecord::Base
has_many :posts
has_many :discussions, :through => :posts, :source => :discussion
end
class Discussion < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
end
Like this, the relation of the user to the discussion is maintained only in the Post model, and not in two places.

Resources