Refactored way of titleizing values from a model (Rails) - ruby-on-rails

I have trouble thinking of a way on how to shorten my process on titleizing values upon rendering them in my view.
I did some custom getters for the following attributes that I need to titleize. Here's my example.
user.rb
class User < ActiveRecord::Base
def department
read_attribute(:department).titleize
end
def designation
read_attribute(:designation).titleize
end
end
This method works but it seems a hassle when I want to do this to other models as well.
Is there a more efficient way to handle this which can be used by other models? If you'll mention Draper (since I don't seem to find on how to titleize selected attributes), how can I accomplish using this gem? But, I would prefer not using a gem but instead, create a custom one.

Not tested this, but you could use a Concern with added modules to handle it
--
Modularity
I found a gem called modularity which basically allows you to pass parameters to a concern & other modules. This means if you can pass the params you wish to "titleize", you may be able to pull it off like this:
#Gemfile
gem 'modularity', '~> 2.0.1'
#app/models/concerns/titleize.rb
module Titleize
extend ActiveSupport::Concern
as_trait do |*fields|
fields.each do |field|
define_method("#{field}") do
self[field.to_sym] = field.titleize
end
end
end
end
#app/models/your_model.rb
Class YourModel < ActiveRecord::Base
include Titleize[:your, :params]
end

If you want those value always titleized, what you are doing is fine, but I would actually apply the method on the setters, not on the getters, so you only do it once per record instead of at each read:
def department=(s)
write_attribute(:department, s.to_s.titleize) # The to_s is in case you get nil/non-string
end
If this is purely for presentation (ie, you want the not titleized version in the database, then it can be done in a presenter using Draper:
class UserDecorator < Draper::Decorator
delegate_all
def designation
object.designation.titleize
end
end
(or another rails presenter).

Related

How to define an application-level global method to be used anywhere in Rails?

I have a method current_org that's defined simply as:
def current_org
Organization.find_by(subdomain: Apartment::Tenant.current)
end
It's always the same, whether it's in a view, controller, a model, or even a service. Since the current tenant is derived from the database connection, I shouldn't have to worry about it being properly scoped. And I find myself using it everywhere.
What's the best way to define a global method in Rails so I can just call current_org from anywhere? Currently my best solution is defining a module in /lib and calling it with CustomHelperMethods.current_org. But I'm looking for something a little cleaner.
I'd put it as a class method in an Organiation model or create a special service/class that fetches it.
class Organization < ApplicationRecord
def self.current_org
find_by(subdomain: Apartment::Tenant.current)
end
end
or
# e.g. in app/services/
class CurrentOrganization
def self.current_org
Organization.find_by(subdomain: Apartment::Tenant.current)
end
end

Why do functions from my Rails plugin not work without specifically requiring?

I need some help with my plugin. I want to extend ActiveRecord::Base with a method that initializes another method that can be called in the controller.
It will look like this:
class Article < ActiveRecord::Base
robot_catch :title, :text
...
end
My attempt at extending the ActiveRecord::Base class with robot_catch method looks like following. The function will initialize the specified attributes (in this case :title and :text) in a variable and use class_eval to make the robot? function available for the user to call it in the controller:
module Plugin
module Base
extend ActiveSupport::Concern
module ClassMethods
def robot_catch(*attr)
##robot_params = attr
self.class_eval do
def robot?(params_hash)
# Input is the params hash, and this function
# will check if the some hashed attributes in this hash
# correspond to the attribute values as expected,
# and return true or false.
end
end
end
end
end
end
ActiveRecord::Base.send :include, Plugin::Base
So, in the controller, this could be done:
class ArticlesController < ApplicationController
...
def create
#article = Article.new(params[:article])
if #article.robot? params
# Do not save this in database, but render
# the page as if it would have succeeded
...
end
end
end
My question is whether if I am right that robot_catch is class method. This function is to be called inside a model, as shown above. I wonder if I am extending the ActiveRecord::Base the right way. The robot? function is an instance method without any doubt.
I am using Rails 3.2.22 and I installed this plugin as a gem in another project where I want to use this functionality.
Right now, it only works if I specifically require the gem in the model. However, I want it the functionality to be included as a part of ActiveRecord::Base without requiring it, otherwise I'd have to require it in every model I want to use it, not particularly DRY. Shouldn't the gem be automatically loaded into the project on Rails start-up?
EDIT: Maybe callbacks (http://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html) would be a solution to this problem, but I do not know how to use it. It seems a bit obscure.
First, I would suggest you make sure that none of the many many built in Rails validators meet your needs.
Then if that's the case, what you actually want is a custom validator.
Building a custom validator is not as simple as it might seem, the basic class you'll build will have this structure:
class SpecialValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# Fill this with your validation logic
# Add to record.errors if validation fails
end
end
Then in your model:
class Article < ActiveRecord::Base
validates :title, :text, special: true
end
I would strongly suggest making sure what you want is not already built, chances are it is. Then use resources like this or ruby guides resources to continue going down the custom validator route.
Answer
I found out the solution myself. Bundler will not autoload dependencies from a gemspec that my project uses, so I had to require all third party gems in an engine.rb file in the lib/ directory of my app in order to load the gems. Now everything is working as it should.
Second: the robot_catch method is a class method.

How to dynamically generate association names?

I am using Ruby on Rails 3.2.2 and the Squeel gem. I have following statements and I am trying to refactoring the my_squeel_query method in a Mixin module (since it is used by many of my models):
# Note: 'article_comment_associations' and 'model_as_like_article_comment_associations'
# refer to database table names.
class Article < ActiveRecord::Base
def my_squeel_query
commenters.
.where{
article_comment_associations.article_id.eq(my{self.id}) & ...
}
end
end
class ModelAsLikeArticle < ActiveRecord::Base
def my_squeel_query
commenters.
.where{
model_as_like_article_comment_associations.article_id.eq(my{self.id}) & ...
}
end
end
My problem is that I can not refactoring article_comment_associations and model_as_like_article_comment_associations statements by generating a dynamic name in the Mixin module. That is, if that was a String I could dynamically generate the related name by using something like "#{self.class.to_s.singularize}_comment_associations" as the following:
class Article < ActiveRecord::Base
include MyModule
end
class ModelAsLikeArticle < ActiveRecord::Base
include MyModule
end
module MyModule
def my_squeel_query
commenters.
.where{
# Note: This code doesn't work. It is just an sample.
"#{self.class.to_s.singularize}_comment_associations".article_id.eq(my{self.id}) & ...
}
end
end
But, since it is not my case, I cannot "build" the name and make the my_squeel_query to be "shared" across models.
How can I dynamically generate association names related to the Squeel gem? Should I think to refactoring in another way? What do you advice about?
Since the DSL is instance_evaled, you can actually say something like:
def my_squeel_query
base = self
commenters.
.where{
# Note: This code does work. Because it's awesome.
__send__("#{base.class.to_s.singularize}_comment_associations").
article_id.eq(my{self.id})
}
end
You can do this if you generate the methods dynamically. The Module.included method is provided for this purpose:
module ModuleAsLikeArticle
def self.included(base)
base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
# ...
end
end
end
This gets triggered when the module is imported with include and allows you to create methods specifically tailored for that.
As a note you might want to use base.name.underscore.singularize for a more readable method name. By convention, method names should not have upper-case in them, especially not as the first character.
Conventional Rails type applications use a different approach, though, instead defining a class method that can be used to create these on-demand:
module ModuleAsLikeArticle
def has_comments
base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
# ...
end
end
end
This would be used like this:
class ModelAsLikeArticle < ActiveRecord::Base
extend MyModule
has_comments
end
Since the method is not created until has_comments is called, you can safely extend ActiveRecord::Base and then insert the appropriate call in all the classes which require that functionality.
I think you might find what you need in the Rails Reflection class (http://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html), which, as the page says, allows you to interrogate ActiveRecord classes about their associations and aggregations.

How to trim input in rails models globally

In my Rails app there are several models where users are posting data to the database. Lots of this data has trailing and leading whitespaces. Is there a way I can globally strip all input's leading and trailing whitespaces?
I'd like to avoid doing this for every field in every model, seems like there could be a global way to handle this during a before_save.
Any used techniques out there?
Thanks
One more gem to do this job: https://github.com/holli/auto_strip_attributes
Also in some cases you want to squish the data user has inputted to get rid of multiple spaces inside the variable. E.g. with names or nicks.
gem "auto_strip_attributes", "~> 1.0"
class User < ActiveRecord::Base
auto_strip_attributes :name, :nick, :nullify => false, :squish => true
end
All the gems and other approaches work a bit the same way by using before_save callback. (Code example is in Jeremys example.) So there might be some issues with custom setters. You can choose to do it with
attributes.each do before_validation do ...
record.send("#{attr_name}=", record.send(attr_name).to_s.strip)
or with
attributes.each do before_validation do ...
record[attribute] = record.send(attr_name).to_s.strip)
First approach will call setter twice (once when setting, once in before_validation). The second will call setter only once but will alter the data after the call to setter.
Here is one simple way to do it on selected attributes:
module ActiveRecord
module Acts
module AttributeAutoStripper
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_attribute_auto_stripper (*names)
class_eval <<-EOV
include ActiveRecord::Acts::AttributeAutoStripper::InstanceMethods
before_validation :auto_strip_selected_attributes
def auto_strip_attributes
#{names.inspect}
end
EOV
end
end
module InstanceMethods
def auto_strip_selected_attributes
if auto_strip_attributes
auto_strip_attributes.each do |attr_name|
self.send("#{attr_name}=", self.send(attr_name).to_s.strip) unless self.send(attr_name).blank?
end
end
end
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecord::Acts::AttributeAutoStripper
and then in your model:
class User < ActiveRecord::Base
acts_as_attribute_auto_stripper :name, :email
end
If users are posting data to the DB through a form, you could create a before filter method that'll strip the parameters. Put that in the Application controller.
I hope this helps :)
This fork of the StripAttributes plugin may do the trick for you:
https://github.com/fragility/strip_attributes
You could create an ActiveRecord subclass with a before_save filter that strips all attributes. Then, make all of your models a subclass of this new class.

how to define/attach rails validations in multiple generations of modules

I've found a way to make this work, but am curious about a better way / the Rails 3 way. (I'm using 2.3.5 still, but hope to migrate around New Year's.)
The situation: I've got two layers of module inheritance, the second layer gets mixed into a Rails model. Both modules define validation methods and I'd like both of them to attach the validations to the base class, but because of the two levels of inheritance, the following doesn't work:
def self.included(base)
base.validate :yadda_yadda
end
When that module is included by another module, the interpreter grinds to a screeching halt because Modules don't know about ActiveRecord::Validations. Including the validations module begs the question of "where is save?" thanks to alias_method.
The following works, as long as you remember to call super whenever you override validate(). I don't trust myself or future maintainers to remember that, so I'd like to use the validate :yadda_yadda idiom instead, if possible.
module Grandpa
def validate
must_be_ok
end
def must_be_ok
errors.add_to_base("#{self} wasn't ok")
end
end
module Dad
include Grandpa
def validate
super
must_be_ok_too
end
def must_be_ok_too
errors.add_to_base("#{self} wasn't ok either")
end
end
class Kid < ActiveRecord::Base
include Dad
validate :must_be_ok_three
def must_be_ok_three
errors.add_to_base("#{self} wasn't ok furthermore")
end
end
Suggestions? Tips for Rails 3 approach? I don't think the validations API has changed that much.
I solved it (when I ran into the same problem, but with something other than validation).
Short answer: you can call send(:included, base) on the module you want to bring in. Within the higher-up included() definition, you need to check whether the base is a Class or a Module.
Why would you ever want to do this? Well, I've got some modules that extract some common functionality out of my models. For instance, the module HasAllocable sets up a polymorphic belongs_to relationship, and a getter/setter pair for a virtual attribute. Now I have another module that needs to pull in HasAllocable, to spare the base classes from having to remember it.
I'd be interested to know whether this smells funny to anyone. I haven't seen anything like it on the web, so I wonder if multiple layers of model inheritance is more of an antipattern.
module Grandpa
def self.included(base)
if base.kind_of?(Class)
base.validate :must_be_ok
end
end
end
module Dad
include Grandpa
def self.included(base)
if base.kind_of?(Class)
# you can do this
#base.send(:include, Grandpa)
# you can also do this
Grandpa.send(:included, base)
# this does not invoke Grandpa.included(Kid)
#super(base)
base.validate :must_be_ok_too
end
end
end
class Kid < ActiveRecord::Base
include Dad
validate :must_be_ok_three
end

Resources