Rails Model Methods not working well - ruby-on-rails

I have methods that i'm trying to test in my models, but they're not working well, it doesn't seem to return false when it should- any suggestions?
class Registration < ActiveRecord::Base
validate :check_duplicate_section
def check_duplicate_section
all_registrations = Registration.all
all_registrations.each do |reg|
puts reg.section_id
if reg.section_id == self.section_id && reg.student_id == self.student_id
errors.add(:registration, "Already exists")
return false
end
return true
end
end
Test File: (#bruce is defined earlier)
class RegistrationTest < ActiveSupport::TestCase
should "not allow invalid student registrations" do
#mike = FactoryGirl.create(:student, :first_name => "Mike")
good_reg = FactoryGirl.build(:registration, :section => #section2, :student => #mike)
bad_reg = FactoryGirl.build(:registration, :section => #section1, :student => #bruce)
bad_reg2 = FactoryGirl.build(:registration, :section => #section2, :student => #mike)
assert_equal true, good_reg.valid?
assert_equal false, bad_reg.valid?
assert_equal false, bad_reg2.valid?

from the looks of what you're trying to do with check_duplicate_section, it's better to use the built in uniqueness validation
validates :section_id, uniqueness: { scope: :user_id }
If you don't want to use this, change your method to
def check_duplicate_section
if Registration.where(section_id: self.section_id, student_id: self.student_id).exists?
errors.add :registration, "Already exists"
end
end
Also, in your tests, you are using build which doesn't save anything to the db. You should use create or better yet, use mocks to force the returned values of your db queries.
The good thing about using the built in validation approach is you don't need to test it because it should work.

Related

Correct methods in Ruby on Rails model

My Rails model has some bad lines. In general, first error is when accessing roles variable from has_access def. And then, second is when making where action.
class Organization < ActiveRecord::Base
has_and_belongs_to_many :organizations_users
belongs_to :user
validates :name, :presence => true, :length => { minimum: 4, maximum: 35 }, uniqueness: true
roles = { :administrator => 1, :support => 2 }
def has_access(chosen_user, role)
self.organizations_users.where('user_id = ? and role = ?', chosen_user.id, roles[role]).exists?
end
def add_user(chosen_user, role)
if !self.has_access(chosen_user, role)
self.organizations_users.create({user_id: chosen_user.id, role: roles[role]})
end
has_access(chosen_user,role)
end
end
I need also to query over organizations_users table to get information about access. How can I fix that?
Simplest fix would be to change
def has_access(chosen_user, role)
where('user_id = ? and role = ?', chosen_user.id, roles[role]).exists?
end
to:
def has_access(chosen_user, role)
Organization.where('user_id = ? and role = ?', chosen_user.id, roles[role]).exists?
end
The reason for this is the where() method is a class method and only accessible from the class itself, IE. Organization.where. BTW, I am assuming (dangerously :) ) That this where clause is used from Organization.
To find or add the organization do:
def has_access(role)
self.organizations_user.find_or_create_by_role(role)
end
This allow you to use:
user = User.has_access("administrator")
user = User.first.has_acess("administrator")
And will do a find in the table user_organizations or create a new entry if can't find one. ActiveRecord relation will take care of the user_id

How do i specify and validate an enum in rails?

I currently have a model Attend that will have a status column, and this status column will only have a few values for it. STATUS_OPTIONS = {:yes, :no, :maybe}
1) I am not sure how i can validate this before a user inserts an Attend? Basically an enum in java but how could i do this in rails?
Now that Rails 4.1 includes enums you can do the following:
class Attend < ActiveRecord::Base
enum size: [:yes, :no, :maybe]
validates :size, inclusion: { in: sizes.keys }
end
Which then provides you with a scope (ie: Attend.yes, Attend.no, Attend.maybe), a checker method to see if certain status is set (ie: #yes?, #no?, #maybe?), along with attribute setter methods (ie: #yes!, #no!, #maybe!).
Rails Docs on enums
Create a globally accessible array of the options you want, then validate the value of your status column:
class Attend < ActiveRecord::Base
STATUS_OPTIONS = %w(yes no maybe)
validates :status, :inclusion => {:in => STATUS_OPTIONS}
end
You could then access the possible statuses via Attend::STATUS_OPTIONS
This is how I implement in my Rails 4 project.
class Attend < ActiveRecord::Base
enum size: [:yes, :no, :maybe]
validates :size, inclusion: { in: Attend.sizes.keys }
end
Attend.sizes gives you the mapping.
Attend.sizes # {"yes" => 0, "no" => 1, "maybe" => 2}
See more in Rails doc
You could use a string column for the status and then the :inclusion option for validates to make sure you only get what you're expecting:
class Attend < ActiveRecord::Base
validates :size, :inclusion => { :in => %w{yes no maybe} }
#...
end
What we have started doing is defining our enum items within an array and then using that array for specifying the enum, validations, and using the values within the application.
STATUS_OPTIONS = [:yes, :no, :maybe]
enum status_option: STATUS_OPTIONS
validates :status_option, inclusion: { in: STATUS_OPTIONS.map(&:to_s) }
This way you can also use STATUS_OPTIONS later, like for creating a drop down lists. If you want to expose your values to the user you can always map like this:
STATUS_OPTIONS.map {|s| s.to_s.titleize }
For enums in ActiveModels you can use this gem Enumerize
After some looking, I could not find a one-liner in model to help it happen. By now, Rails provides Enums, but not a comprehensive way to validate invalid values.
So, I opted for a composite solution: To add a validation in the controller, before setting the strong_params, and then by checking against the model.
So, in the model, I will create an attribute and a custom validation:
attend.rb
enum :status => { your set of values }
attr_accessor :invalid_status
validate :valid_status
#...
private
def valid_status
if self.invalid_status == true
errors.add(:status, "is not valid")
end
end
Also, I will do a check against the parameters for invalid input and send the result (if necessary) to the model, so an error will be added to the object, thus making it invalid
attends_controller.rb
private
def attend_params
#modify strong_params to include the additional check
if params[:attend][:status].in?(Attend.statuses.keys << nil) # to also allow nil input
# Leave this as it was before the check
params.require(:attend).permit(....)
else
params[:attend][:invalid_status] = true
# remove the 'status' attribute to avoid the exception and
# inject the attribute to the params to force invalid instance
params.require(:attend).permit(...., :invalid_status)
end
end
To define dynamic behavior you can use in: :method_name notation:
class Attend < ActiveRecord::Base
enum status: [:yes, :no, :maybe]
validates :status, inclusion: {in: :allowed_statuses}
private
# restricts status to be changed from :no to :yes
def allowed_statuses
min_status = Attend.statuses[status_was]
Attend.statuses.select { |_, v| v >= min_status }.keys
end
end
You can use rescue_from ::ArgumentError.
rescue_from ::ArgumentError do |_exception|
render json: { message: _exception.message }, status: :bad_request
end
Want to place another solution.
#lib/lib_enums.rb
module LibEnums
extend ActiveSupport::Concern
included do
validate do
self.class::ENUMS.each do |e|
if instance_variable_get("#not_valid_#{e}")
errors.add(e.to_sym, "must be #{self.class.send("#{e}s").keys.join(' or ')}")
end
end
end
self::ENUMS.each do |e|
self.define_method("#{e}=") do |value|
if !self.class.send("#{e}s").keys.include?(value)
instance_variable_set("#not_valid_#{e}", true)
else
super value
end
end
end
end
end
#app/models/account.rb
require 'lib_enums'
class Account < ApplicationRecord
ENUMS = %w(state kind meta_mode meta_margin_mode)
include LibEnums
end

Validation Error in RSpec before(:each) block has weird behavior

REVISED: the error here was that I was storing several different models in the same hash. This was an internal way with how I was constructing the array. Anyway, I apologize for the error here. There was no way one could have answered the question with how I asked it.
So I have an RSpec before(:each) block in a controller spec. My example model has a status field and the following validation:
class Model < ActiveRecord::Base
STATI = [ "vacant", "deleted", "deactivated"]
...
validates :status, :inclusion => { :in => STATI }
...
end
And in my spec, I have the following code.
describe Controller do
...
describe "some methods" do
before(:all) do
#models = []
10.times { #models << Factory(:model) }
end
before(:each) do
#models.each { |m| m.update_attributes(:status => "vacant") }
end
...
end
end
When I run the spec, all the other describe blocks run fine. It pulls an an error to the effect of:
ActiveRecord::RecordInvalid:
Validation failed: Status is not included in the list
and points to the line where it says m.update_attributes(:status => "vacant").
Thank you for any help.
I would try the following in your model definition:
class Model < ActiveRecord::Base
STATI = %w[vacant deleted deactivated]
...
validates :status, :inclusion => STATI
...
end
The %w is preferred syntax for creating an array of strings, and allows the removal of " and , from your array definition.
You do not need the :in => for an inclusion validation.

Rails Object in Database is Invalid

I have a model named Tickets that being saved to the database even when
invalid. This is stopping me from using validations to help prevent
duplicate data being saved to the DB. In script/console
>> Ticket.last.valid?
=> False
>> Ticket.first.valid?
=> False
If I try to see what errors are associated with this invalid object
>> Ticket.last.errors.each{|attr,msg| puts "#{attr} - #{msg}\n" }
=> {}
So does anyone know how it's possible to save an invalid object to the
database, and how can I find what is making the object invalid?
Ticket.rb (model)
class Ticket < ActiveRecord::Base
belongs_to :whymail
belongs_to :forms
attr_accessible :to_email, :to_email, :from_email, :subject, :body
validates_uniqueness_of :to_email, :scope => [:body, :from_email]
validates_presence_of :to_email
validates_presence_of :from_email
validates_presence_of :subject
validates_presence_of :body
def after_create
if self.valid?
whymail = Whymail.find(:first, :include => :user, :conditions => ['(email = ?)', self.to_email.upcase ] )
if !whymail.nil?
self.whymail_id = whymail.id
self.save
MyMailer.deliver_forward(whymail.user.email, self.from_email, self.to_email, self.subject, self.body)
end
end
end
end
One part of this question was answered, second was not. Can anyone see problems with this model that may allow it to save even though it is invalid??
It is possible to skip validations. How are you saving it? Is it part of a nested form?
In any case, you should look at the errors like this:
>>t = Ticket.last
>>t.valid?
>>t.errors.each{|attr,msg| puts "#{attr} - #{msg}\n" }
The way you have it above, you are getting a new object with the second Ticket.last call and validation hasn't been run on that one, so you can't see what the errors are.
Try something like:
t = Ticket.last
t.save
puts t.errors.full_messages.inspect
The errors object won't be populated until you try to save the activerecord object.

validate presence of has_and_belongs_to_many

Hi i'm using a has_and_belongs_to_many in a model.
I want set the valitor of presence for kinds.
and set the max number of kinds per core to 3
class Core < ActiveRecord::Base
has_and_belongs_to_many :kinds, :foreign_key => 'core_id', :association_foreign_key => 'kind_id'
end
how can i do?
thanks
validate :require_at_least_one_kind
validate :limit_to_three_kinds
private
def require_at_least_one_kind
if kinds.count == 0
errors.add_to_base "Please select at least one kind"
end
end
def limit_to_three_kinds
if kinds.count > 3
errors.add_to_base "No more than 3 kinds, please"
end
end
You could try something like this (tested on Rails 2.3.4):
class Core < ActiveRecord::Base
has_and_belongs_to_many :kinds, :foreign_key => 'core_id', :association_foreign_key => 'kind_id'
validate :maximum_three_kinds
validate :minimum_one_kind
def minimum_one_kind
errors.add(:kinds, "must total at least one") if (kinds.length < 1)
end
def maximum_three_kinds
errors.add(:kinds, "must not total more than three") if (kinds.length > 3)
end
end
... which works in the following way:
require 'test_helper'
class CoreTest < ActiveSupport::TestCase
test "a Core may have kinds" do
core = Core.new
3.times { core.kinds << Kind.new }
assert(core.save)
end
test "a Core may have no more than 3 kinds" do
core = Core.new
4.times { core.kinds << Kind.new }
core.save
assert_equal(1, core.errors.length)
assert_not_nil(core.errors['kinds'])
end
test "a Core must have at least one kind" do
core = Core.new
core.save
assert_equal(1, core.errors.length)
assert_not_nil(core.errors['kinds'])
end
end
Obviously the above isn't particularly DRY or production-ready, but you get the idea.

Resources