Validation failed Class must exist - ruby-on-rails

I have been (hours) trouble with associations in Rails. I found a lot of similar problems, but I couldn't apply for my case:
City's class:
class City < ApplicationRecord
has_many :users
end
User's class:
class User < ApplicationRecord
belongs_to :city
validates :name, presence: true, length: { maximum: 80 }
validates :city_id, presence: true
end
Users Controller:
def create
Rails.logger.debug user_params.inspect
#user = User.new(user_params)
if #user.save!
flash[:success] = "Works!"
redirect_to '/index'
else
render 'new'
end
end
def user_params
params.require(:user).permit(:name, :citys_id)
end
Users View:
<%= form_for(:user, url: '/user/new') do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :citys_id, "City" %>
<select name="city">
<% #city.all.each do |t| %>
<option value="<%= t.id %>"><%= t.city %></option>
<% end %>
</select>
end
Migrate:
class CreateUser < ActiveRecord::Migration[5.0]
def change
create_table :user do |t|
t.string :name, limit: 80, null: false
t.belongs_to :citys, null: false
t.timestamps
end
end
Message from console and browser:
ActiveRecord::RecordInvalid (Validation failed: City must exist):
Well, the problem is, the attributes from User's model that aren't FK they are accept by User.save method, and the FK attributes like citys_id are not. Then it gives me error message in browser saying that "Validation failed City must exist".
Thanks

Try the following:
belongs_to :city, optional: true
According to the new docs:
4.1.2.11 :optional
If you set the :optional option to true, then the presence of the
associated object won't be validated. By default, this option is set
to false.

This comes a bit late but this is how to turn off this by default in rails 5:
config/initializers/new_framework_defaults.rb
Rails.application.config.active_record.belongs_to_required_by_default = false
In case you don't want to add optional: true to all your belongs_to.
I hope this helps!

You need to add the following to the end of the belongs_to relationship statement:
optional: true
It is possible to set this on a global level so that it works in the same way as older versions of rails, but I would recommend taking the time to manually add it to the relationships that really need it as this will cause less pain in the future.

I found out a solution to the problem "Validation failed: Class must exist" and it's better than use:
belongs_to :city, optional: true
4.1.2.11 :optional
If you set the :optional option to true, then the presence of the associated object won't be validated. By default, this option is set to false.
cause you still make a validation in application level. I solve the problem making my own validation in create method and changing user_params method:
def create
#city = City.find(params[:city_id])
Rails.logger.debug user_params.inspect
#user = User.new(user_params)
#user.city_id = #city.id
if #user.save!
flash[:success] = "Works!"
redirect_to '/index'
else
render 'new'
end
end
def user_params
params.require(:user).permit(:name)
end
I didn't test this code, but it works in another project mine. I hope it can help others!

Rails 5
If you have a belongs_to relationship to :parent then you have to pass an existing parent object or create a new one then assign to children object.

belongs_to :city, required: false

params.require(:user).permit(:name, :citys_id)
It's a mistake, isn't it? (citys_id vs city_id)

Rails.application.config.active_record.belongs_to_required_by_default = false
This works because the Rails 5 has true by default
to disable you go under Initilizers then click on the New_frame-work and turn the true to false

Related

Association between post, user, and comment on Ruby on Rails

I'm trying to learn Ruby on Rails, an I'm kinda stuck with associaton.
My project is to create a simple blog with three table. User, Post, and Comment.
In my understanding, after associationg several table with foreign key, rails would automatcily find user_id and post_id. But everytime I try to build comments, the user_id is nil.
Here's my model:
class User < ApplicationRecord
has_many :posts
has_many :comments
validates :name, presence: true, length: { minimum: 5 }, uniqueness: true
validates :password, presence: true, length: { minimum: 5 }
end
class Post < ApplicationRecord
belongs_to :user
has_many :comments
validates :title, presence: true
validates :body, presence: true, length: {minimum: 10}
end
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
validates :body, presence: true
validates :user_id, presence: true
validates :post_id, presence: true
end
Here is the screenshot when I try to create a comment:
As you can see, the post_id is not nil but the user_id is nil.
I try to input user_id manualy and it work as intended. But I can't find out how to create comment with automatic user_id and post_id.
In my understanding, after associationg several table with foreign key, rails would automatcily find user_id and post_id. But everytime I try to build comments, the user_id is nil.
There is no truth to that assumption. Rails will not automatically assign your assocations - how should it even know what user/post you want to associate the comment with?
Typically the way you would construct this is to have a nested route:
resources :posts do
resources :comments,
only: [:create]
shallow: true
end
This creates the route /posts/:post_id/comments so that we know which post the user wants to comment on - you would then adjust your forms so that it posts to the nested route:
# app/views/comments/_form.html.erb
<%= form_with(model: [post, comment]) do |f| %>
# ...
<% end %>
# app/views/posts/show.html.erb
# ....
<h2>Leave a comment</h2>
<%= render partial: 'comments/form',
locals: {
post: #post,
comment: #comment || #post.comments.new
}
%>
Getting the user who's commenting would typically be done by getting it from the session through your authentication system - in this example the authenticate_user! callback from Devise would authenticate the user and otherwise redirect to the sign in if no user is signed in.
You then simply assign the whitelisted parameters from the request body (from the form) and the user from the session:
class CommentsController
before_action :authenticate_user!
# POST /posts/1/comments
def create
# This gets the post from our nested route
#post = Post.find(params[:post_id])
#comment = #post.comments.new(comment_params) do |c|
c.user = current_user
end
if #comment.save
redirect_to #post,
status: :created
notice: 'Comment created'
else
render 'comments/show',
status: :unprocessable_entity,
notice: 'Could not create comment'
end
end
private
def comment_params
params.require(:comment)
.permit(:foo, :bar, :baz)
end
end
This is typically the part that Rails beginners struggle the most with in "Blorgh" tutorials as it introduces a resource thats "embedded" in another resource and its views and several advanced concepts. If you haven't already I read it would really recommend the Getting Started With Rails Guide.
you can create a comments as below:
user = User.find 2
post = user.posts.where(id: 2).first
comment = post.comments.build({comment_params}.merge(user_id: user.id))
Hope this will help you.

Rails5. Nested attributes are not going to database

class User < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
end
class Address < ApplicationRecord
belongs_to :user
end
<%= form_for #user do |f| %>
.... // some filed here everything fine
<%= f.fields_for :address do |a| %>
<%= a.text_field :city %> // this field is not appear
<% end %>
<% end %>
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.valid?
#user.save
else
redirect_to root_path
end
end
private
def user_params
params.require(:user).permit(:id, :name, :email, :password, :password_confirmation, :status, :image, :address_attributes => [:id, :city, :street, :home_number, :post_code, :country])
end
end
So like you can see above I have two classes and one form, when I am trying display fields for Address class I can not do it in that way. I took this example from https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
I was trying different combination like for example using User.new and Address.new in form definition it not working as well, I was able display all fields in that situation but I wasn't able to save Address data to table, because of "unpermited address".
Can someone explain what I am doing wrong? Or at least give me please some hints.
[SOLVED]
I should learn how to read documentations properly. Excalty like #Srack said I needed just use build_address method. I checked documentation rails api again and on the end of page there was examples says to create User class like this:
class User < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
def address
super || build_address
end
end
and that solved my issue.
Thank you.
You'll have to make sure there's an address instantiated for the user in the new view. You could do something like:
def new
#user = User.new
#user.build_address
end
You should then see the address fields on the form.
The nested_fields_for show the fields for a record that's been initialised and belong to the parent. I think the latter is why your previous attempts haven't worked.
FYI build_address is an method generated by the belongs_to association: http://guides.rubyonrails.org/association_basics.html#methods-added-by-belongs-to

Ruby on Rails: ActiveRecord::AssociationTypeMismatch

I am getting an ActiveRecord::AssociationTypeMismatch error when trying to submit a record.
Subject(#88982676) expected, got String(#20223000)
View:
<%= f.collection_select :subject, Subject.order(:subject), :subject, :subject, {prompt: "Select a subject"}, {class: "form-control"} %>
Controller:
def create
#homework = current_user.homeworks.build(homework_params)
if #homework.save
redirect_to homeworks_path
else
render 'new'
end
end
...
def homework_params
params.require(:homework).permit(:subject, :description, :date, :completed_at)
end
Model: Homework.rb
class Homework < ActiveRecord::Base
validates :subject, presence:true
belongs_to :subject
def completed?
!completed_at.blank?
end
end
Subject.rb
class Subject < ActiveRecord::Base
has_many :homeworks
def to_s
subject
end
end
This use to work but suddenly doesn't. I did change the name of the table to "subject" and changed the views and controller accordingly. It appears to be looking for id now? Subject is a string. Any advice? Thanks.
According to your association models, your homework attributes should look like this:
subject_id:integer description:string date:datetime completed_at:datetime
Therefore, you should permit subject_id, instead of subject in your homework_params
As for collection_select method, it should be something like this:
f.collection_select :subject_id, Subject.order(:subject), :id, :subject

Ruby On Rails Change Column to Null, Not Work?

I command in Terminal to change on column in database to not null, but It seems not work.
rails g migration change_column_null :Speaker, :surname, false
I got a file ChangeColumnNull But inside, it is nothing.
class ChangeColumnNull < ActiveRecord::Migration
def change
end
end
Lecture Controller (Def Create):
class CplecturesController < ApplicationController
layout 'cp_layout'
def create
#lecture = Lecture.new(lecture_params)
#lecture.save
redirect_to #lecture
end
private
def lecture_params
params.require(:lecture).permit(:lecture_title, :lecture_day, :column, :start_time, :end_time, :registered_speakers, :guest_speakers, :description)
end
end
Forms
<%= form_for :lecture, url:lectures_path do |f| %>
<form>
<div class="form-group">
<%=label_tag "Lecture Title" %><br>
<%= f.text_field :lecture_title, :class => "form-control", :placeholder => "Example: Why is Wordpress the best?" %>
</div>
Erase that migration and write a blank migration with a better name and then fill it out by setting a default value. If you have a default value it will never be null.
rails g migration ChangeColumnOnTableName
Then inside that migration do the following:
change_column :name_of_table, :name_of_column, :data_type_of_column, :null => false
If you're only worried about it being null based on what a user enters, you could simply add a validation that requires it. In your model:
validates :name_of_column, presence: true
if you are using latest ruby (2.5+) Here is the migration script to change fields from NOT NULL to NULL
class ChangeEmailPasswordToNullableUsers < ActiveRecord::Migration[5.2]
def change
change_column_null :users, :email, true
change_column_null :users, :password, true
end
end

RoR 4 belongs_to foreign_key not checked

I'm a newbie in RoR and I'm trying to make a simple experiment. I want to have a User model and a Role model. Each user a single Role, but a single Role may refer to multiple Users.
So, here are my models:
class Role < ActiveRecord::Base
has_many :user
end
class User < ActiveRecord::Base
has_one :role
end
And here are migrations:
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.string :name, null: false, index: true, unique: true, limit: 16
t.integer :permissions, null: false
end
end
end
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email, null: false, index: true, unique: true, limit: 128
t.string :password_digest, null: false, limit: 40
t.string :password_salt, null: false, limit: 40
t.string :screen_name, default: '', limit: 32
t.belongs_to :role, index: true, foreign_key: true
t.timestamps null: false
end
end
end
What I want is to make it raise an exception when I try to connect user with a role that does not exist:
user = User.create(email: 'user#example.com', password_digest: 'pwd_dig',
password_salt: 'salt', role_id: 10)
Unfortunately this works and the new user is created, no matter that the role with id 10 does not exist.
So, how can I force foreign_key check here?
And another question about that. If I try to do like this:
user = User.create(email: 'user#example.com', password_digest: 'pwd_dig',
password_salt: 'salt', role: role)
it raises an exception because role does not have attribute user_id. There is no way to do like this, does it?
I'm a newbie in RoR
Welcome!!
#app/models/role.rb
class Role < ActiveRecord::Base
has_many :users
end
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :role
validates :role_id, presence: true, on: :create
validate :check_role, on: :create
private
def check_role
errors.add(:role_id, "Role doesn't exist") unless Role.exists? role_id
end
end
This will allow you the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#roles = Role.all
end
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.require(:user).permit(:email, :etc, :role)
end
end
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.email_field :email %>
<%= f.collection_select :role_id, #roles, :id, :name %>
<%= f.submit %>
<% end %>
Because you're new, I'll give you some info:
1. Association
Your association is slightly incorrect - you'll be best using a belongs_to/has_many relationship:
The belongs_to will assign the foreign_key in your User model, allowing you to both reference and validate against it.
-
2. Custom validator
Secondly, you'll be able to use a validation to check whether the role has been set correctly.
Each time a Rails model is saved, it calls a series of validators you've set -- allowing you to call such functionality as checking whether a role exists.
What you can do is add a custom validation:
class User < ActiveRecord::Base
belongs_to :role
validate :existance_of_role
private
def existance_of_role
if role_id && !Role.exists?(role_id)
errors.add(:role, "must already exist.")
end
end
end
Also you need to use belongs_to :role. has_one would place the foreign_key in the relationship on the Role model instead of on the user.
http://requiremind.com/differences-between-has-one-and-belongs-to-in-ruby-on-rails/
If the user must have a role.
Also if you want to enforce on the database level that the user has a role you would add a NOT NULL constraint to users.role_id.
Run: rails g migration AddNotNullConstraintToUsers
And then edit the migration file created:
class AddNotNullConstraintToUsers < ActiveRecord::Migration
def change
change_column_null(:users, :role_id, false)
end
end
After running the migration you can change the validation so that it will add an error also when the role_id is nil.
private
def existance_of_role
errors.add(:role, "must already exist.") unless Role.exists?(role_id)
end
You could do the same thing with validates_presence_of :role but that will not enforce that the Role is pre-existing.
You can add this validator at your User model
class User < ActiveRecord::Base
has_one :role
validate :role_id_exists
def role_id_exists
return false if Role.find_by_id(self.role_id).nil?
end
end

Resources