Rails 4: before_save callback not working - ruby-on-rails-4

I have a polymorphic lookups table with a child ContractType model. I have a before_save callback in the ContractType model that sets a category, but it doesn't seem to be working.
class Lookup < ActiveRecord::Base
validates :value, presence: true
validates :category, presence: true
end
class ContractType < Lookup
before_save { self.category = "contract_type" }
end
Then I open the rails c:
> c = ContractType.create(value: "test")
> c.errors.messages
=> { :category => ["can't be blank"] }
I don't get any errors, just a failed validation. As far as I know, the syntax looks correct, it's just that the before_save callback doesn't seem to be working...
Am I missing something obvious here?

I found the problem...validations take place BEFORE the save, so it always "failed" validation and stopped before the before_save callback took place.
The solution is to use a before_validation callback.
class ContractType < Lookup
before_validation { self.category = "contract_type" }
end
My misunderstanding of the order of events.

I know you found your solution, but think about your problem and solution logically. You are hard coding the category value. It means that the user cannot leave it blank. It will always be filled. So why have a validation for it in the first place? Just remove this:
validates :category, presence: true
In the before_save callback, you will know the value will be assigned. before_validation does have use cases. For example, a user enters a value for a currency attribute and you want to format the currency prior to validation. But in your case, before_validation is not needed.

Related

Activerecord uniqueness validation ruby on rails 4.2

I just started trying my hands on Ruby on rails . i have created a mode states and want that every state name must remain unique so i used
uniqueness: true
as per http://guides.rubyonrails.org/active_record_validations.html .
As the above document says that the validation is invoked automatically when object.save is called. but When i try to save the objects with same state_name value , no exception is thrown and the record is saved. Can one please help where i am doing it wrong.
Model code
class State < ActiveRecord::Base
acts_as_paranoid
validates_presence_of :state_name ,uniqueness: true
end
Controller code
def create
#stateName = params[:stateName];
#state = State.new();
#state.state_name=#stateName;
if(#state.save())
resp = { :message => "success" }
else
resp = { :message => "fail" }
end
respond_to do |format|
format.json { render :json => resp }
end
end
Thanks in advance for helping!
If you want uniqueness check, change
validates_presence_of :state_name ,uniqueness: true
to
validates :state_name, uniqueness: true
If you want uniqueness and presence check both, use
validates :state_name, uniqueness: true, presence: true
An alternate syntax is shown below, however, syntax shown above should be preferred
validates_uniqueness_of :fname
validates_presence_of :fname
Also, as per documentation, please note following with respect to usage of uniqueness: true
It does not create a uniqueness constraint in the database, so it may
happen that two different database connections create two records with
the same value for a column that you intend to be unique. To avoid
that, you must create a unique index on both columns in your database.
The validation happens by performing an SQL query into the model's
table, searching for an existing record with the same value in that
attribute.
What this means is that it is possible that if multiple users are trying to save records concurrently, there is a possibility records with duplicate state_name can get created as each save is happening on different thread

Form validation exclusion for a word

I am trying to add a validation to a simple form in a Rails 4.1 project. I need to reject a form submission if a text field contains a forbidden word. My other validations work but not this one. So if the user enters no value or too long of a value it is rejected and the error message is displayed.
I have it setup similarly to the edge guide (http://edgeguides.rubyonrails.org/active_record_validations.html#exclusion) but the form submit still goes through if I enter "My favorite color is red." What should I do differently?
My model:
class ColorEntry < ActiveRecord::Base
validates :body, presence: true
validates :body, length: { maximum: 255 }
validates :body, exclusion: { in: %w(red) }
end
Try to use a regex validation for format with the "without" attribute. So, if it does not match, the form can't be submitted.
validates :body, format: {without: /example/}
Other syntax
validates_format_of :body, without: /example/
More references in:
http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html

Why do these models and methods crash my server/stack level too deep?

I'm new to Ruby-on-Rails and have been trying to slowly understand how everything works but I've run into a brick wall at this point.
I have the models
class User < ActiveRecord::Base
validates :username, {:uniqueness => true, :presence => true}
validates_presence_of :password_digest, :on => :create
has_one :player, dependent: :destroy
has_secure_password
has_many :planets, through: :player
end
class Player < ActiveRecord::Base
has_many :planets, dependent: :destroy
belongs_to :user
validates_presence_of :race, on: :create
end
class Planet < ActiveRecord::Base
belongs_to :player
end
In my UserController I create a User with login and password and that works just fine. Then I redirect to my PlayerController where I have a simple radio button to select race (just one right now) and then create the player and add it to the current user. That also works just fine. Now the problem is when I try to add a Planet into the player. I do this in the same controller method (not sure if this is proper but it needs to populate with a default planet to a new player).
def generate_attributes
{
class: 'HomeWorld',
land: 500,
ore: 100,
agriculture: 0,
industry: 0,
housing: 10
}
end
def create
#current_user.player = Player.new(player_params)
#current_user.player.planets.create generate_attributes
redirect_to action: :home
end
On #current_user.player.planets.create generate_attributes my server completely crashes. Previously when I removed both belongs_to :user from player and belongs_to :player from planet I would get a Stack Level Too Deep exception.
There's obviously something I'm doing horrendously wrong but I can't figure out what it is. I attempted to debug but once I get to a certain point within the rails source code my debugger would disconnect and I'd have to force the server to stop.
Any insight would be greatly appreciated.
Based on your generate_attributes method, it looks like you might have a field called class on your Planet model. This is almost definitely causing your problems, and you need to use a different name than class.
The reason is that in Ruby, just about everything has a class, e.g.:
>> "foo".class
# => String
>> User.first.class
# => User
However, for your Planet model, class is being set to a string. Rails uses a lot of introspection into class names to deal with associations; because you're linking this Planet to a Player with a belongs_to, I'm assuming Rails is checking the class of the Planet object to make sure it's correct, seeing "HomeWorld" instead of Planet like it expects, and raising an exception as a result.
(And even taking associations out of the picture, you'll still invariably run into problems using class as a field name).

Rails 4, not saving #user.save when registering new user

When I try to register an user, it does not give me any error but just cannot save the user.
I don't have attr_accessible. I'm not sure what I am missing. Please help me.
user.rb
class User < ActiveRecord::Base
has_secure_password
validates :email, presence: true,
uniqueness: true,
format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }
validates :password, presence: true, length: {minimum: 6}
validates :nickname, presence: true, uniqueness: true
end
users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params) # Not saving #user ...
if #user.save
flash[:success] = "Successfully registered"
redirect_to videos_path
else
flash[:error] = "Cannot create an user, check the input and try again"
render :new
end
end
private
def user_params
params.require(:user).permit(:email, :password, :nickname)
end
end
Log:
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"x5OqMgarqMFj17dVSuA8tVueg1dncS3YtkCfMzMpOUE=", "user"=>{"email"=>"example#example.com", "password"=>"[FILTERED]", "nickname"=>"example"}, "commit"=>"Register"}
(0.1ms) begin transaction
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'example#example.com' LIMIT 1
User Exists (0.1ms) SELECT 1 AS one FROM "users" WHERE "users"."nickname" = 'example' LIMIT 1
(0.1ms) rollback transaction
Regarding our short discussion in the comments, it appears that one or two things are happening to cause #user.save to return false:
One of the validation rules are failing
A callback within your model is returning false, thus halting processing and rolling back the transaction
There are a few quick ways to debug the validations. I figured I could describe them so you could learn a few options.
A. Change the call within the if statement to instead use the bang method of save:
if #user.save!
This will cause the app to raise an exception if validation fails, displaying the validation errors within the browser on your screen. In this particular scenario, you'd want to remember to remove the ! after you're done debugging because you probably don't want the final version of your app doing that.
Or...
B. Within the else statement, add this line:
raise #user.errors.to_yaml
This will display the validation errors within the browser on the screen. Of course, remember to remove this line after you're done debugging.
Or...
C. Within the else statement, add this line and then run the form post:
puts #user.errors.to_yaml
This will display the validation errors within your console. You'll want to remember to remove this line after you're done debugging, but it's "less worse" if you happen to forget because at least the extra info is only output to STDOUT.
You may want to try each of these just to get a little practice and to see what your options are in simple debugging scenarios like this.
High chances that error is in password confirmation. You use has_secure_password from Rails, which automagically handles password confirmation for you. And here is the problem - you don't have it before user creation. Thus just add. For details check out similar question on has_secure_password
And check, that you have password_digest:string in users table :)

Strong parameters for nested attributes returns "unpermitted parameters" when empty array

Assuming a User model
using Rails4 with strong_parameters.
class User < ActiveRecord::Base
has_secure_password
accepts_nested_attributes_for :identity
// rest of code omitted for brevity
end
If I refer to the guide I should be able to do
def user_params
params.require(:user).permit(:email, identity_attributes: [])
end
to allow mass_assignment of each identity_attributes whatever their names or number. But this run in a "Unpermitted parameters: identity_attributes"
But if I specify the identity_attributes it works
def user_params
params.require(:user).permit(:email, identity_attributes: [:last_name, :first_name])
end
I have many attributes in Identity, I would be able to mass_assign them through User without specifying all of them.
Am I missing something ? Is it a bug ?
Cheers
You have to specify the identity's attributes you want to updated, including the :id of the identity entity.
you will have something like that :
def user_params
params.require(:user).permit(:email, identity_attributes: [:id, :last_name, :first_name])
end
if you don't specify the :id, Rails will try to create an entity instead of updating it. I spend all the week-end struggling on a simple one-to-many relationship using accepts_nested_attributes_for because I didn't specified the id in the permitted attributes.