Rails: Access class method of a model inside the same model - ruby-on-rails

In order to access the same array in different parts of my app, I stored the array in the corresponding Model, using class method to retrieve the array.
In the code bellow, codes are used in views (to generate the select drop down) and in the same model (to validate passed value).
class Request < ActiveRecord::Base
validates :code, presence: true, inclusion: { in: self.codes }
def self.codes
return ["OBJ", "SER", "REC"]
end
end
But using this, generates the following error:
undefined method `codes' for #<Class:0x000001074ddc50>
Even removing the self. in the inclusion, doesn't solve the problem (undefined local variable).
Do you have an idea?

Your codes method is declared after you've used it in your validation - when the validation line is executed the method has not yet been defined, hence the error.
If you place the validation after the codes method it should work.

You can define it as a constant at a top of your model
class Request < ActiveRecord::Base
CODES = ["OBJ", "SER", "REC"]
Than you can access it like this Request::CODES
validations will look like this
validates :code, presence: true, inclusion: { in: CODES }

Related

Using ruby on rails validation helpers inside custom validations

Let's say I have lots of attributes that can only have a specific set of string values.
Typically we'd see the following.
class User < ApplicationRecord
validates :foo, inclusion: { in: ['some', 'array'] }
validates :bar, inclusion: { in: ['another', 'array'] }
validates :moo, inclusion: { in: ['one_more', 'array'] }
end
I have lots of these types of validations in my model and I want to DRY them up. So I tried the below but I get a error undefined method 'validates' for #User:0x00007fdc10370408.
class User < ApplicationRecord
VALIDATION_ENUMS = {
foo: %w[foo1 foo2],
bar: %w[bar1 bar2]
}.freeze
validate :validate_enums
def validate_enums
VALIDATION_ENUMS.each_key do |attribute|
validates attribute, inclusion: { in: VALIDATION_ENUMS[attribute] }
end
end
end
How do I get access to the ActiveModel::Validations helper methods from within my function?
Or is there a better way to do this?
Remember that validates is a class method, only executed once when the class is loaded to establish what will be validated. validate is calling an instance method.
A better way might be to execute the DRY code immediately when loading the class.
class User < ApplicationRecord
validate_enums = {
foo: %w[foo1 foo2],
bar: %w[bar1 bar2]
}.freeze
validate_enums.each do |key, array|
validates key, inclusion: { in: array }
end
Note that as you don't reference validate_enums ever again, you don't need to make it a class constant, which is why I didn't.
But you don't really save any lines and add complexity, so I'd stick with the explicit validates, myself.
This approach won't fly. The validation methods are class methods that modify the class itself while you are writing an instance method that get called on an instance of the class when #valid? is called.
If you want to dynamically add existing validations to the class you need to create a class method:
class User < ApplicationRecord
def self.add_inclusion_validations(hash)
# don't use each_key if you're iterating over both keys and values
hash.each do |key, values|
validates_presence_of key, in: values
end
end
add_inclusion_validations(
foo: %w[foo1 foo2],
bar: %w[bar1 bar2]
)
end
Of course you could also just skip the method completely:
class User < ApplicationRecord
{
foo: %w[foo1 foo2],
bar: %w[bar1 bar2]
}.each do |key, values|
validates_presence_of key, in: values
end
end
If what you instead want is to write a validation method that uses the existing functionality of other validations you would create a ActiveRecord::Validator or ActiveRecord::EachValidator subclass and use the existing validations there. But you really need to start by reading the guides and API docs so that you have a base understanding of how that works.

Accessing model variable in validates, format

I have the (simplified) model structure below:
class Customer < ApplicationRecord
validates :full_name, format: { without: /[^a-zA-Z .,']+/, message: "cannot contain " + /[^a-zA-Z .,']+/.match(self.full_name).to_s}
end
I want to validate the user-provided full_name with a regular-expression and if the validation fails, I want to show which part of the full_name fails the regular-expression validation.
However, this returns "undefined method full_name" and I tried a bunch of other things for self.full_name but can't seem to figure out how to pass that data there.
How can I do this? Thanks for any feedback and answer, in advance.
According to the docs, Rails allows to pass a Proc message. Thus, in your case it's possible to customise the message:
class Customer < ApplicationRecord
validates :full_name,
format: {
without: /[^a-zA-Z .,']+/,
message: ->(object, data) do
"cannot contain " + /[^a-zA-Z .,']+/.match(object.full_name).to_s
end
}
end

Passing through an association attributes in ruby on rails

I have a model user that has one traversal. The Traversal model has an attribute, frame. I can access it using
user.traversal.frame
but I seem to recall there is a ruby on rails means for making the following work.
user.traversal_frame
without an extra method in the User model i.e. without doing
def traversal_frame
self.traversal.frame
end
Is there another way to do this?
Use delegate method. Something like following
class User < ActiveRecord::Base
has_one :traversal
delegate :frame, to: :traversal
end
You can then use it like following
user.frame # => Same as user.traversal.frame
Note:- when user has no traversal then user.frame will throw an error raises NoMethodError: undefined method 'frame' to fixed this use following
delegate :frame, to: :traversal, allow_nil: true
To access it using user.traversal_frame you have to use prefix option as below
delegate :frame, to: :traversal, prefix: true, allow_nil: true

How to make the validation(only accepts alphanumeric) global for my model in ruby

I am currently new in ruby on rails and I have gem called rails admin. I want the validation not repeatable, i want it to save in one method and make it global so that in my model I can call the validation format.
I added a def in my application controller and inside of it i declare it as global but when I type special characters, it will be add.
Note: My validation is only allowed alphanumeric or underscore..
Expected Output: It wont add unless the input is alphanumeric or underscore
Question: Is my global variable wrong? How could I make a global variable so that I will just call it
Model
class ActivityType < ApplicationRecord
has_many :activities
validates :name, presence: true, length: { maximum: 20 },
:uniqueness => true,
format: { with: /\A[\w\_]+\z/ }
validates :description, presence: true,
format: { with: /\A[\w]+\z/ }
end
RegexValidations
module RegexValidations
def self.alphanumeric_underscore
{ with: /\A[\w\_]+\z/ }
end
end
Validation are done at model level for their respective attributes.
Defining methods in application_controller doesn't make it global, but accessible to all controllers inheriting from it.
Your model validation validates should be enough to validate format of the string in column.
Edit/Improvisation:
If you want to make the regex dry/not repeat for every model. Create a module in your lib folder say:
module RegexValidations
def self.alphanumeric_underscore
{ with: /\A[\w\_]+\z/ }
end
end
And in your model include it and say:
include RegexValidations
validates :description, presence: true, format: RegexValidations.alphanumeric_underscore
This way you can write multiple regex in one file and use it in every model keeping it DRY.
PS: unfortunately i couldn't test it..but it should work. Let me know if you encounter any error.

How to understand the colon operator usage in a Ruby class

I am learning both Ruby (2.3.x) & Rails (4.x). I was going through the Ruby On Rails Tutorial and I encountered this syntax and am having trouble reading it:
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true
end
Does this class define validates as a method which takes in a :name symbol and a hash presence:true? The same thing applies to line 3.
Or it is something entirely else? All attempts to run it resulted in:
uninitialized constant ApplicationRecord.
I looked at the source(maybe?) but am still not clear.
This is a special DSL introduced by ApplicationRecord. What you are actually doing is calling those methods inside class during declaration. It adds those validations to your class, so whenever you try to save a record it will fail if you don't have email or name
Try this
user = User.new
user.save
user.valid? # false
And try to do the same without validates.
If it will make things more clear for you, you can try write this class like this
class User < ApplicationRecord
validates(:name, presence: true)
validates(:email, presence: true)
end
validates is implemented as a class method in ActiveModel::Validations.
The ActiveModel::Validations module is included in ApplicationRecord, therefore you are able to call that method when your User class is loaded.
validates accepted an array and treats the last element of that array as an options hash (if the last element is an hash).
validates is a pre-defined helper that Active Record offers to use in Rails to make the validation work easier, this way you can with some single lines of code manage several validations of several attributes.
As it's a helper within Rails it's also a method defined in the ActiveModel module, in the core of the framework, see: active_model/validations.rb
The most common is the presence attribute which you're facing the trouble with, that specifies that the attribute used isn't empty, doing it in the Ruby way through the blank? method to check if the value passed isn't blank nor nil.

Resources