Refactoring validation methods and callbacks - ruby-on-rails

I am using Ruby on Rails 3.0.7 and I have tree classes what behavior is almost the same (and also the code in them model files). All those have a name and a description attribute, run same validation methods and for both there is a before_save callback that maintains data consistent providing the same functions.
I would like to refactor validation methods and callbacks in a separated class\model (I think I have to locate them related files in the \lib folder of my application).
What I have to do to make that? What code I have to add in my classes and what in the refactoring class\model?

Well, you could just make a super class from which your three models inherit. I tend to put the abstract base class in app/models alongside the models themselves.
# app/models/thing.rb
class Thing < ActiveRecord::Base
# common code goes here, such as
before_save ...
validates_length_of :foo
end
# app/models/red_thing.rb
class RedThing < Thing
# methods specific to RedThing go here
end
# app/models/blue_thing.rb
class BlueThing < Thing
# methods specific to BlueThing go here
end
If your Things have many differences such that it doesn't make sense to group them like this, you'd want to use a module instead, which is a only bit more complicated.

Rails guides has info here:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#creating-custom-validation-methods

Related

How to use a gem class as a model in rails?

I'm new to ruby and rails. I 've created a rails app that uses the acts_as_votable gem. The gem contains a class Vote that I'd like to use as a model for my app. Is it possible?
I have tried using rails g scaffold Vote. That way I got an empty Vote model and the appropriate controller and views. However, because acts_as_votable had already created a database table called votes /votes showed the pre-existing votes without their attributes. The same applies to votes/new, it didn't show any input fields as the new Vote class was empty.
I also thought about copying the gem class in my app but I knew it was a horrid idea.
As long as you have the gem in your Gemfile, you should be able to access the Vote class by specifying its namespace :
ActsAsVotable::Vote
So you should be able to use it as you use other models.
It is also possible to monkey-patch the class, for instance in an initializer :
ActsAsVotable::Vote.send( :include, MyModule )
Then in another file :
module MyModule
extend ActiveSupport::Concern
included do
# class eval any macro you want...
end
module ClassMethods
def foo
# define class methods...
end
end
def bar
# define instance methods...
end
end
However, i would advise against doing this. It is risky to build whole parts of your business logic on a class you don't own, and is not designed to be used directly ! As long as you just add minor features, it's okay, but if you really need custom behavior, just go ahead and re-implement an 'acts_as_votable` functionnality yourserlf.
It is not so difficult nor long, and you will own your logic, which would shield you from unpredictable changes in the Vote class when upgrading.
You can try to use it with ActsAsVotable::Vote, but i think it should be used in combination with an existing Model.

Restructure bulky class

I have an ActiveRecord model Reservation.
It got to the point that the class is to large and does too much.
I would like to split it into a few different ones and place those under the Reservation module.
Unfortunately this will break the app.
At this moment I see following options:
namespace it to something like ReservationConcerns or similar
add the functionality to the Reservation class itself, but physically move it to the subdir (Reservation would be in app/models/reservation.rb, Reservation::Pipeline would be in app/models/reservation/pipeline.rb etc).
So the question is how to structure the different concerns of a feature already having it as one single, bulky class without breaking the app.
If you want to split up a Ruby class into different components without changing its public interface, one solution is to use modules:
# app/models/reservation.rb
class Reservation < ActiveRecord::Base
# associations, validations, etc.
include Pipeline
end
# app/models/reservation/pipeline.rb
module Reservation::Pipeline
def some_pipeline_method
# ...
end
def other_pipeline_method
# ...
end
end
ActiveRecord also provides observers, which "is a great way to reduce the clutter that normally comes when the model class is burdened with functionality that doesn't pertain to the core responsibility of the class". Observers often make heavy use of the ActiveModel::Dirty methods.
These suggestions are complimentary: modules can help you group your interface into more local chunks, while observers can make backend details more self-contained. From here, it's difficult to be more specific without knowing exactly what pieces you have that you're trying to break out.

Rails: keeping DRY with ActiveRecord models that share similar complex attributes

This seems like it should have a straightforward answer, but after much time on Google and SO I can't find it. It might be a case of missing the right keywords.
In my RoR application I have several models that share a specific kind of string attribute that has special validation and other functionality. The closest similar example I can think of is a string that represents a URL.
This leads to a lot of duplication in the models (and even more duplication in the unit tests), but I'm not sure how to make it more DRY.
I can think of several possible directions...
create a plugin along the lines of the
"validates_url_format_of" plugin,
but that would only make the
validations DRY
give this special string its own model, but this seems like a very
heavy solution
create a ruby class for this special string, but how do I get
ActiveRecord to associate this class
with the model attribute that is a
string in the db
Number 3 seems the most reasonable, but I can't figure out how to extend ActiveRecord to handle anything other than the base data types. Any pointers?
Finally, if there is a way to do this, where in the folder hierarchy would you put the new class that is not a model?
Many thanks.
UPDATE:
One potential solution using Matt's mixin suggestion below (and using the URL example). Note, this is closer to psuedocode than real ruby and is intended to convey the principle rather than perfect syntax.
First, create a url mixin:
module Url
def url_well_formed?
[...]
end
def url_live?
[...]
end
end
And in a Site model, include this module:
Class Site < ActiveRecord:Base
include Url
validate :url_well_formed?
end
And when I need to check that the site at the URL is live, do...
if site.url_live?
[...]
end
The thing this doesn't solve is how to make the unit tests DRY. If I have another model, say Page, that also uses the Url mixin, it will still need a duplicate set of unit tests for the URL. Of course, I could put these in a helper, but that seems messy.
Is there a more fundamental solution, or is this as good as it gets?
Create an abstract model:
class CommonBase < ActiveRecord::Base
self.abstract_class = true # makes the model abstract
validate_format_of :url_field, :with => /.../
end
Inherit your models from the abstract model:
class User < CommonBase
end
class Post < CommonBase
end
You could create a module with all the common methods, and import the module? Google for mixins.

What Are The Advantages and Disadvantages of Putting ActiveRecord Methods into Modules?

What are the advantages and disadvantages of creating a module like:
module Section
def self.included(base)
base.class_eval do
has_many :books
end
end
def ensure_books
return false if books <= 0
end
end
...where ActiveRecord methods are used in the module instead of directly on the class(es) they belong to?
Should modules be used for methods like this?
The most obvious advantage is that you can take functionality that is shared and put it into a single place. This is just a general advantage of keeping your code organized and modularized (no pun intended) – and you should, of course, do that
Using Active Record methods does not make these Modules special in any way.
The most obvious disadvantage is that your code, as written, is a little more complex. You can't use validates_presence_of in a module directly because it does not inherit from ActiveRecord::Base. (Rails 3 is supposed to make it easier to selectively extend your own classes/modules with bits of ActiveRecord-Functionality
Instead, you need to call the Active-Record-Methods on your model class when your model is included:
module FooHelper
def self.included(other)
other.send(:has_many, :foos)
end
end
So the prime disadvantage is that your code gets a little harder to read.
If you are just breaking up a single class into separate parts and don't need to reuse the code somewhere else, you could use the concerned_with-pattern which works by reopening classes.
On the other hand, If you need more functionality, like configuration parameters for your extension, consider writing a plugin
This code can be shared by models (classes).

pros/cons of a ::Base class (rather than acts_as_foo)

Rather than an acts_as_foo syntax**, I'm toying with the idea of extending ActiveSensor::Base < ActiveRecord::Base and then extending this base class with all the errors/validations/class methods/whizbangs.
Proposed: #active_sensor.rb gem
module ActiveSensor
include ActiveRecord
autoload :VERSION, 'active_sensor/version'
autoload :ActiveSensorError, 'active_sensor/base'
autoload :Base, 'active_sensor/base'
autoload :Validations, 'active_sensor/validations'
end
Pros:
I think it looks cleaner.
Architecture and class build-up emulates ActiveRecord
I have a lot of subclassing going on... want to check whether a component is hardware using :is_a? method.
Cons:
holding an extra inheritance class layer in memory... never used independently
not very conventional (only seen 1 other rails plugin do this)
Any advice? Is creating a new ::Base class just dumb? If so, why?
** The acts_as_foo pattern is common for Rails gems. This class method is added to every AR object, which loads and extends class and instance methods when loading the class.
I think you've already answered yourself. Quite simply, if you extend Base then you add the functionality to every Active Record class in your system, whereas if you use acts_as_foo - you can add it only to the models that require it.
Obviously form this, it's better to use acts_as_foo if you only want the functionality for some of your models... but if you want it in every model, then feel free to extend Base.

Resources