Validation fails, field must exist with allow_nil - ruby-on-rails

I'm creating a blog where articles can be responses to other articles. Articles can also be part of groups. However articles do not have to be in a group or be a response to another article.
I'm attempting to follow the Rails docs to create articles as self-joined records.
I created user, group and article scaffolds:
bin/rails g scaffold user username:string email:string birthday:date
bin/rails g scaffold group name:string user:references
bin/rails g scaffold article user:references parent:references title:string subheading:string body:text pub_date:datetime group:references hidden:boolean prompt:boolean
I'm trying to use allow_nil in the model validation.
class Article < ApplicationRecord
belongs_to :user
belongs_to :parent, class_name: "Article"
has_many :replies, class_name: "Article", foreign_key: "parent_id"
belongs_to :group
validates :parent, length: {minimum: 1}, allow_nil: true
validates :group, length: {minimum: 1}, allow_nil: true
end
However when I run the db:seed:
user1 = User.create(username: "pete01",email: "pete01#gmail.com",
birthday:"1980-01-30")
article1 = Article.create!(user:user1, parent:nil, title:"My First Article",
subheading:"This is important", body:"The body of my first article",
pub_date:"2015-12-26", group:nil, hidden:false, prompt:false)
I get this error:
ActiveRecord::RecordInvalid: Validation failed: Parent must exist, Group must exist
Is there somewhere else where I should be telling Rails it does not need to validate Group and Parent?

Solution is to find the file new_framework_defaults.rb, change this to false:
Rails.application.config.active_record.belongs_to_required_by_default = false

#app/models/article.rb
class Article < ActiveRecord::Base
belongs_to :parent, class_name: "Article"
belongs_to :group
validates :parent, presence: true, allow_nil: true
validates :group, presence: true, allow_nil: true
end
Several problems for you:
You're validating parent & group -- these are associative objects.
Your error says "[Object] must exist", which means your validation works -- Rails cannot find a "nil" association (it's expecting objects).
What you should have is either to validate parent_id & group_id, or to validate the presence of the associative object with something like presence.
I've included the following validations I would use:
validates :parent, presence: true, allow_nil: true
validates :group, presence: true, allow_nil: true
You could also try:
validates :parent_id, length: { minimum: 1 }, allow_nil: true
validates :group_id, length: { minimum: 1 }, allow_nil: true
Articles can also be part of groups
You'll probably want to use a has_and_belongs_to_many association for it then:
#app/models/article.rb
class Article < ActiveRecord::Base
has_and_belongs_to_many :groups
end
#app/models/group.rb
class Group < ActiveRecord::Base
has_and_belongs_to_many :articles
end
You'll need a join table called articles_groups with the columns article_id & group_id:
You can create the migration as follows:
$ rails g migration CreateArticlesGroups
# db/migrate/create_articles_groups__________.rb
class CreateArticlesGroups < ActiveRecord::Migration
def change
create_table :articles_groups, id: false do |t|
t.belongs_to :article, index: true
t.belongs_to :group, index: true
end
end
end
$ rake db:migrate
This will allow you to populate the associative objects like this:
#article = Article.find params[:article_id]
#group = Group.find params[:id]
#article.groups << group

This is most likely to be the result of migrating to Rails 5, where the default for belongs_to relations changed. See this answer for details,

allow_nil: true is an option to a validator, not to the validates method. You are using the length: key which will call the LengthValidator with the {minimum: 1} hash as arguments (similar to using validates_length_of).
E.g., use
validates :parent, length: {minimum: 1, allow_nil: true}
instead of
validates :parent, length: {minimum: 1}, allow_nil: true
Note: If using multiple validators, you will need to specify allow_nil for each of them.

Try to remove your validations. Also check your migration for Article. Maybe you have null: false there for group_id and parent_id
class Article < ApplicationRecord
belongs_to :user
belongs_to :parent, class_name: "Article"
has_many :replies, class_name: "Article", foreign_key: "parent_id"
belongs_to :group
end

Related

Rails use model in the same namespace for belongs_to reference, how to reference model from outside

I working on a Rails application, currently we structure the app by modules. Right now we have 2 separate model for users: User and Freight::Customer::User.
I have a new model Freight::Customer::MembershipStatus looks like this:
class Freight::Customer::MembershipStatus < ActiveRecord::Base
belongs_to :customer, class_name: 'Freight::Customer'
belongs_to :created_by, class_name: 'User'
validates :from, presence: true
validates :to, presence: true
validates :customer, presence: true
validates :status, presence: true
end
In this case, the created_by is reference to User. But when the code run membership_status.created_by, rails try to look for the Freight::Customer::User, I think it because Rails try to look for model within the same module first.
Is there a way to config this model to use the outer User model class?
You can get user class using this type, try this.
class Freight::Customer::MembershipStatus < ActiveRecord::Base
belongs_to :customer, class_name: 'Freight::Customer'
belongs_to :created_by, class_name: '::User'
validates :from, presence: true
validates :to, presence: true
validates :customer, presence: true
validates :status, presence: true
end

Validate associated object (lazy validation)

I'm trying to solve validation of associated object with condition.
User doesn't need to have filled author_bio until he is author. So app needs to ensure, that author can't create post without author_bio and author_bio can't be deleted if user already created any post.
class User < ApplicationRecord
has_many :posts, foreign_key: 'author_id', inverse_of: :author
validates :author_bio, presence: { if: :author? }
def author?
posts.any?
end
end
class Post < ApplicationRecord
belongs_to :author, class_name: 'User', inverse_of: :posts, required: true
end
Unfortunately this doesn't validate author on creation of new post:
user = User.first
user.author_bio
=> nil
post = Post.new(author: user)
post.valid?
=> true
post.save
=> true
post.save
=> false
post.valid?
=> false
So how can I prevent creating of new post by user without author_bio? I can add second validation to Post model, but this is not DRY. Is there any better solution?
The answer here seems to be use of validates_associated once you have your associations correctly set up (including inverse_of which you have, but stating for others, rails in many cases misses them or creates them incorrectly)
so to adjust the classes here:
class User < ApplicationRecord
has_many :posts, foreign_key: 'author_id', inverse_of: :author
validates :author_bio, presence: { if: :author? }
def author?
posts.any?
end
end
class Post < ApplicationRecord
belongs_to :author, class_name: 'User', inverse_of: :posts
validates :author, presence: true
validates_associated :author
end
Now when you try running what you did before:
user = User.first
user.author_bio
=> nil
post = Post.new(author: user)
post.valid?
=> false
post.save
=> false
Does not allow you to save since author_bio is empty
Only thing to watch out there was to set up correct associations, otherwise rails was confused and skipping validation on User class since it thought the relationship is not yet in existence.
NOTE: I removed required: true from belongs_to since in rails 5 is default, therefore you won't need validates :author, presence: true either only in rails 5.

one-to-many through referance table

I'm trying to understand how to implement one-to-many relationship through reference table. I'm looking on this guide I though just write on one model has_many so it will be one-to-many but I'm not completely sure (I wrote something but it's not working). Anyway I'm doing this to save for me a table, and doing it right and not just working.
The model is as following:
Microposts, :id, :content
Tag, :id, :name
Tag_microposts, :tag_id, :micropost_id
Article, :id, :text
Article_microposts, :article_id, :micropost_id
I can do two microposts tables with the id of the tag/article. But I think doing it like this is better and righter.
In the end what's interesting me is to get microposts through tag model. So in the tag_controller be able to do:
def index
#tag = Tag.find(params[:id])
#microposts = #tag.microposts
end
Some code:
class Tag < ActiveRecord::Base
...
has_many :tag_microposts, foreign_key: :tag_id, dependent: :destroy
has_many :microposts, through: :tag_microposts, source: :micropost
...
end
class TagMicropost < ActiveRecord::Base
validates :tag_id, presence: true
validates :micropost_id, presence: true
end
class Micropost < ActiveRecord::Base
belongs_to :user
belongs_to :tag
validates :content, presence: true, length: {minimum: 10, maximum: 250}
validates :user_id, presence: true
end
May I ask why you are using a reference table for this? You can do a one-to-many association with only the two models you are associating. If you want to associate a tag with many posts you can just do this in your models.
class Tag < ActiveRecord::Base
has_many :microposts
end
class Micropost < ActiveRecord::Base
belongs_to :tag
#added in edit
belongs_to :article
validates :content, presence: true, length: {minimum: 10, maximum: 250}
validates :user_id, presence: true
end
This should let you do:
#tag.microposts
Just fine. The forgien key will be stored in your Micro post model so be sure to add the column. You can use an active migration for that. Call the column tag_id, rails should take care of the rest.
Edit*
A added the article association. The problem you raised is only relevant if you need to get the article/tag given the micropost. The code to do that is still pretty simple with this model.
#tag ||= #micropost.tag
Using the conditional assignment operator like this will only assign #tag if the association is there. If you give me more specifics about how these models will be used I can give you a better answer.

Why do rails migrations contain belongs_to, but not has_many?

I just spent an hour and half troubleshooting my app trying to figure out why
User.articles
was throwing errors. The models looked okay:
class User < ActiveRecord::Base
has_secure_password
has_many :articles
validates :email, presence: true, uniqueness: true
validates :name, presence: true, uniqueness: true
end
and
class Article < ActiveRecord::Base
validates :title, presence: true, uniqueness: true
validates :content, presence: true
belongs_to :user
has_many :article_categories
has_many :categories, through: :article_categories
end
In the end, the problem was that the migration for articles did not have the line
t.belongs_to :user
Along the way, I also tried putting this line in the user migration
t.has_many :articles
but it threw an error.
Why do migrations only need the belongs_to side of the relationship and not has_many?
Migrations provide a .belongs_to because that actually defines a column, the foreign key linking the tables. On the other hand, has_many doesn't actually do anything to the table itself; including it in a migration would have absolutely non value or effect.
The confusion is on your part. The migrations doesn't have to have has many and belongs to because it's already defined in the models. For the relationship to work, generate a migration that adds user_id(integer) column to Articles table.
i.e
rails generate migration add_user_id_to_articles
class AddUserIdToArticles < ActiveRecord::Migration
def change
add_column :articles, :user_id, :integer
end
end

Cant get has_many and underscore working

i am stack for days with this, tried everything.
I am trying doing a simple has many associations, but it refuse to work:
what i need is that each orb get an orb_type associated with it.
i generate the scaffolds:
rails generate scaffold orb_type nome
rails generate scaffold orb nome:string descr:text orb_type_id:integer
Make the rake db:migrate,
change the models:
class Orb < ActiveRecord::Base
has_many :orb_types
validates_associated :orb_types
attr_accessible :descr, :nome, :orb_type_id
validates :nome, uniqueness: true, presence: true
end
class OrbType < ActiveRecord::Base
attr_accessible :nome
validates :nome, uniqueness: true, presence: true
belongs_to :orb
end
And then tryed to make it work:
$ rails c
1.9.3-p448 :001 > tipo = OrbType.new nome: "Planeta"
1.9.3-p448 :002 > tipo.save
1.9.3-p448 :003 > tipo = OrbType.find(1)
1.9.3-p448 :004 > planeta = Orb.new nome:"Testname", descr: "TestDescr"
1.9.3-p448 :005 > planeta.orb_type = tipo
and in the last line i get the error:
NoMethodError: undefined method `each' for #<OrbType:0x00000003dc02a0>
Whats the deal? the underscore and rails "conventions" are giving me headaches.
I saw a lot of another similar topics, but none of theirs solutions worked!
Your association is the wrong way round. (Your scaffolds are fine, just need to switch the belongs_to and has_many around.)
Change your models like so:
class Orb < ActiveRecord::Base
belongs_to :orb_type
validates_associated :orb_types
attr_accessible :descr, :nome, :orb_type_id
validates :nome, uniqueness: true, presence: true
end
class OrbType < ActiveRecord::Base
has_many :orbs
attr_accessible :nome
validates :nome, uniqueness: true, presence: true
end
Now an orb can be given a type and a type can be given to many orbs.
Having an orb_type_id in your Orb model is part of the problem. You're saying that Orb has many OrbTypes but the orb_type_id would essentially just allow one and the OrbType belongs to the Orb which means that the OrbType would need the orb_id.
Assuming a many to many relationship is the goal you most likely need an association model:
class Orb < ActiveRecord::Base
has_many :orb_types, :through => :orb_associations
has_many :orb_accociations
validates_associated :orb_types
attr_accessible :descr, :nome, :orb_type_id
validates :nome, uniqueness: true, presence: true
end
class OrbAccociations < ActiveRecord::Base
belongs_to :orb
belongs_to :orb_type
end
class OrbType < ActiveRecord::Base
attr_accessible :nome
validates :nome, uniqueness: true, presence: true
has_many :orb_associations
has_many :orbs, :through => :orb_associations
end

Resources