How do a custom method applicable to various models - ruby-on-rails

I have the following method called capitalizeEachWord. Inside this method there is an attribute called company
class BusCompany < ActiveRecord::Base
attr_accessible :company
before_save :capitalizeEachWord
validates :company,presence: true,
uniqueness: { case_sensitive: false },
format: /^([a-zA-z0-9]+\s?){1,}$/
def capitalizeEachWord
self.company=self.company.downcase.split.map(&:capitalize).join(' ')
end
end
I would like that this method not use the attribute company directly, but receives this attribute as a parameter for doesn't do it dependent of the model BusCompany. Something as the following. The problem is that this method I going to use in various models and don't want to write it in each model but use the inheritance
class BusCompany < ActiveRecord::Base
attr_accessible :company
before_save :capitalizeEachWord(self.company)
validates :company,presence: true,
uniqueness: { case_sensitive: false },
format: /^([a-zA-z0-9]+\s?){1,}$/
def capitalizeEachWord(attribute)
self.attribute=self.attribute.downcase.split.map(&:capitalize).join(' ')
end
end

Add the following code into config/initializers/capitalizer.rb
module Capitalizer
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def capitalize(*attributes)
#attributes_to_capitalize = attributes
before_save :capitalize_each_word
end
def attributes_to_capitalize
Array.new(#attributes_to_capitalize)
end
end
def capitalize_each_word
self.class.attributes_to_capitalize.each do |attr|
if value = send(attr)
self.send("#{attr}=", value.strip.titleize)
end
end
end
end
And then in your class:
class BusCompany < ActiveRecord::Base
include Capitalizer
capitalize :company
...
end

First, I'd recommend you override the setter for company instead of using error prone callbacks, like this:
class BusCompany < ActiveRecord::Base
# you can also use #titleize instead of capitalize each word
# also use try, in case `arg` is nil
def company=(arg)
super arg.try(:titleize)
end
end
Then you can use modules to wrap this functionality into a reusable unit. Throw this in a file in your concerns folder, or just in to the models folder:
module CapitalizedSetter
def capitalize_setter(*attr_names)
# for each attr name, redifine the setter so it supers the titleized argument instead
attr_names.each do |attr|
define_method(:"#{attr}=") { |arg| super arg.try(:titleize) }
end
end
end
Finally extend it into the desired models:
class BusCompany
extend CapitalizedSetter
capitalized_setter :company
end

Related

Rails model common validations inherited from abstract base class but unique field validations occur on subclass

I'm trying to understand if it's possible, given two models that share some methods and fields, to put the validations that are common between the two of them in an abstract base class. Below code represents a simplified version of my situation.
There are two classes of invoice line items: sales and collections. These line items share a common field invoice_amount I want to validate the presence of the invoice_amount from an abstract base class but fields that are not common to both models get validated by the subclass.
class Collection < InvoiceLineItem
belongs_to :invoice
validates :c_number, :invoice_number, :invoice_date, presence: true
.
.
.
end
class Sale < InvoiceLineItem
belongs_to :invoice
.
.
.
end
class InvoiceLineItem < ApplicationRecord
self.abstract_class = true
def self.inherited(base)
super
base.send(:extend, NumberFormatter)
base.send(:commafy, :invoice_amount)
end
def invoice_amount
self[:invoice_amount] || '0.00'
end
def export_date
invoice_date
end
end
I've tried several things to get this to work with no success. Some of my attempts included adding the following code to my InvoiceLineItem base class
def self.inherited(base)
base.class_eval do
validates :invoice_amount, presence: true
end
super
base.send(:extend, NumberFormatter)
base.send(:commafy, :invoice_amount)
end
and
def self.inherited(base)
base.class_eval do
base.send(:validates, :invoice_amount, presence: true)
end
super
base.send(:extend, NumberFormatter)
base.send(:commafy, :invoice_amount)
end
and this as described here (https://medium.com/#jeremy_96642/deep-rails-how-to-use-abstract-classes-6aee9b686e75) which seemed promising because it described exactly what I want to do however it does not work for me.
with_options presence:true do
validates :invoice_amount
end
In all these cases the code executes without error however if I write a test like below it fails because validation succeeds!
RSpec.describe Collection, type: :model do
it "Requires an invoice amount" do
result = Collection.create(invoice_amount: nil, c_number: 'CUST012', invoice_number: 'INV001', invoice_date: Date.new(1999, 1,1))
expect(result.valid?).to be false
expect(result.errors[:invoice_amount]).to include("can't be blank")
end
end
I'm not really interested in hearing answers about how it should be done using composition instead of inheritance I won't go into the details but just assume that it has to be done using inheritance. It seems like it should be possible but I'm not sure and I can't find any source on the internet that has a solution that actually works.
Any help would be very much appreciated!
I figured out my issue, it turns out this code was working:
class InvoiceLineItem < ApplicationRecord
self.abstract_class = true
def self.inherited(base)
super
base.send(:extend, NumberFormatter)
base.send(:commafy, :invoice_amount)
end
with_options presence: true do
validates :invoice_amount
end
def invoice_amount
self[:invoice_amount] || 0.00
end
def export_date
invoice_date
end
def debtor_number_up_to_space
debtor_number.split[0]
end
end
the validator was using the method invoice_amount which was shadowing the field on the model so invoice_amount wasn't being seen as nil but as 0.00 thus my test validation was passing correctly.
Once I removed the || 0.00 I could get the test to fail.

Rails Custom Validation method

I am fairly new to Rails validations. I have an Activity model that has many attributes (listed in attributes array below). I need to validate that every activity has a name and a at least one of the other attributes. I was think of something like the following but it looks a little messy. Any advice?
class Activity < ActiveRecord::Base
belongs_to :user
validate :valid_activity
def valid_activity
attributes = [reps, distance, meal, post_meal, yoga, reminder, duration]
if name.present? && self.include? (activity)
end
end
end
You would create a new validator class like so
class ActivityValidator < ActiveModel::Validator
def valid_activity
attributes = [:reps, :distance, :meal, :post_meal, :yoga, :reminder, :duration]
unless name.present? && attributes.any?{ |a| self.activity == a }
errors[:user] << 'Need to add an activity'
end
end
end
Then in your user.rb file, include the validator module and use the validates_with method.
include ActiveModel::Validations
validates_with ActivityValidator

How to make helpers for models

I have this private method in my User model
class User < ActiveRecord::Base
attr_accessible :email, :fullname, :password, :username
has_secure_password
before_create { generate_token(:remember_token) }
.
.
private
def generate_token(col)
begin
self[col] = SecureRandom.urlsafe_base64
end while User.exists?(col => self[col])
end
end
How can i make generate_token available In other models?
Thanks
many options here are some:
use a plain, simple ruby module.
module TokenRememberable
private
def generate_token(col)
begin
self[col] = SecureRandom.urlsafe_base64
end while User.exists?(col => self[col])
end
end
class User < ActiveRecord::Base
include TokenRememberable
before_create { generate_token(:remember_token) }
end
for more complex functionnality, use ActiveSupport::Concern :
module TokenRememberable
extend ActiveSupport::Concern
# thanks to Concern, this block wil be evaluated
# in the context of the including class
included do
before_create { generate_token(:remember_token) }
end
private
def generate_token(col)
begin
self[col] = SecureRandom.urlsafe_base64
end while self.class.exists?(col => self[col])
end
end
class User < ActiveRecord::Base
include TokenRememberable
end
extract the functionnality in a dedicated class, and possibly use composition
# we only need a class method here, but you can also build
# full-fledged objects as you need
class TokenGenerator
def self.generate_token
# your generation logic here
end
end
class User
def after_initialize
#token_generator = TokenGenerator # or TokenGenerator.new( self ), for instance
end
attr_reader :token_generator
delegate :generate_token, to: :token_generator # optionnaly use delegation
end
NOTE :
this is not really a rails issue, more a Ruby one. You should document yourself more on the language you are using... Modules are a very common idiom. I can give you some good reference books if you need
a few approaches:
slightly dependent:
do:
class User < ActiveRecord::Base
before_create { User::Helpers.new(self).generate_token(:remember_token) }
class Helpers < Struct.new(:user)
def generate_token(col)
begin
user.send("#{col}=", SecureRandom.urlsafe_base64)
end while User.exists?(col => user.send(col))
end
end
end
independent: don't pass the user object to the class, just make the method render a random token and assign it from the model.

Proper attribute initialization in Rails when an attribute is wrapped by another class

Suppose we have the following classes
class Foo < ActiveRecord::Base
attr_accessible :name, :bars, :bazs
def bars=(bars)
baz = []
bars.each { |b| barz << Baz.new(bar:b, magic_number: 123) }
end
def bars
bazs.map(&:bar)
end
end
class Bar < ActiveRecord::Base
attr_accesible :name
end
class Baz < ActiveRecord::Base
attr_accesible :magic_number, :bar
has_one :bar
end
which is the "rails" way to declare an initialization method so when a Foo is created from a hash the bazs are initialized. For example
Foo.new(name:"foo", bars:[Bar.new(name:"b1"), Bar.new(name:"b2")])
You cannot use after_initialize because self[:bars] returns nil. The other option is to overwrite the initialize method but it is not recomended by the Rails documentation and I cannot use barz= because after initialization barz returns nil, I have to use self[:barz]=. The other option would be to declare a class constructor method that does the proper initialization by calling the setter but it does not seem to be the Ruby way (Foo.from(name:"foo", bars:[Bar.new(name:"b1"), Bar.new(name:"b2")])).
Thanks
OK What I finally did was to declare everything as attr_accessible and override the initialize method
class Foo < ActiveRecord::Base
attr_accessible :name, :bars, :bazs
def initialize(attributes = nil, options = {})
super
self.bars = attributes[:bars]
end
def bars=(bars)
self.bazs = []
self.bars.each { |b| self.bazs << Baz.new(bar:b, magic_number: 123) }
end
def bars
self.bazs.map(&:bar)
end
end
One thing to notice that I didn't know was that everytime that I need to called a getter/setter it needs to be done using self.

Rails ActiveRecord: validate single attribute

Is there any way I can validate a single attribute in ActiveRecord?
Something like:
ac_object.valid?(attribute_name)
You can implement your own method in your model. Something like this
def valid_attribute?(attribute_name)
self.valid?
self.errors[attribute_name].blank?
end
Or add it to ActiveRecord::Base
Sometimes there are validations that are quite expensive (e.g. validations that need to perform database queries). In that case you need to avoid using valid? because it simply does a lot more than you need.
There is an alternative solution. You can use the validators_on method of ActiveModel::Validations.
validators_on(*attributes) public
List all validators that are being used to validate a specific
attribute.
according to which you can manually validate for the attributes you want
e.g. we only want to validate the title of Post:
class Post < ActiveRecord::Base
validates :body, caps_off: true
validates :body, no_swearing: true
validates :body, spell_check_ok: true
validates presence_of: :title
validates length_of: :title, minimum: 30
end
Where no_swearing and spell_check_ok are complex methods that are extremely expensive.
We can do the following:
def validate_title(a_title)
Post.validators_on(:title).each do |validator|
validator.validate_each(self, :title, a_title)
end
end
which will validate only the title attribute without invoking any other validations.
p = Post.new
p.validate_title("")
p.errors.messages
#=> {:title => ["title can not be empty"]
note
I am not completely confident that we are supposed to use validators_on safely so I would consider handling an exception in a sane way in validates_title.
I wound up building on #xlembouras's answer and added this method to my ApplicationRecord:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def valid_attributes?(*attributes)
attributes.each do |attribute|
self.class.validators_on(attribute).each do |validator|
validator.validate_each(self, attribute, send(attribute))
end
end
errors.none?
end
end
Then I can do stuff like this in a controller:
if #post.valid_attributes?(:title, :date)
render :post_preview
else
render :new
end
Building on #coreyward's answer, I also added a validate_attributes! method:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def valid_attributes?(*attributes)
attributes.each do |attribute|
self.class.validators_on(attribute).each do |validator|
validator.validate_each(self, attribute, send(attribute))
end
end
errors.none?
end
def validate_attributes!(*attributes)
valid_attributes?(*attributes) || raise(ActiveModel::ValidationError.new(self))
end
end

Resources