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.
Related
We know that Rails 5 added ApplicationRecord as an abstract class which was inherited by our models (ActiveRecord).
But basically, I think every technical requirement we do with ApplicationRecord, we can also do with ActiveRecord::Base. For instance:
module MyFeatures
def do_something
puts "Doing something"
end
end
class ApplicationRecord < ActiveRecord::Base
include MyFeatures
self.abstract_class = true
end
So now every model will be attached the behaviors of MyFeatures. But we can also achieve this in Rails 4:
ActiveRecord::Base.include(MyFeatures)
So what is the benefit of using ApplicationRecord, do you think it is necessary to add ApplicationRecord?
While it may seem the same in basic Rails applications, there actually is an important difference once you begin to use rails engines, plugins / gems or direct methods from ActiveRecord::Base.
ActiveRecord::Base.include(MyFeatures) mixes in the features directly into ActiveRecord::Base and it is present there forever for all later uses of ActiveRecord::Base (it cannot be "unmixed") and there is no way to get the original ActiveRecord::Base anymore in any code after the include. This can easily lead to problems if some of the mixed in feature changed the default ActiveRecord behavior or if e.g. two engines / gems tried to include same-named methods.
On the other hand, the ApplicationRecord approach makes the features present only for the classes (models) that inherit from it, other classes, as well as direct use of ActiveRecord::Base stay pristine, uncluttered by the module features.
This is especially important when engines or rails plugins are used as it allows them to separate their own model logic from the main application's model logic which was not possible before ApplicationRecord.
All of this is also nicely described in this blog post and this github comment.
This is to expand on #BoraMa's answer, and to, hopefully, clear up some confusion around ActiveRecord::Base.abstract_class.
ActiveRecord::Base.abstract_class goes back to at least Rails 3.2.0 (http://api.rubyonrails.org/v3.2.0/classes/ActiveRecord/Inheritance/ClassMethods.html), which was released on January 20, 2012.
Rails 4.0.0 improved the documentation: http://api.rubyonrails.org/v4.0.0/classes/ActiveRecord/Inheritance/ClassMethods.html
So, to everyone who thinks ApplicationRecord is radically new, it's not. It is an improvement, not a breaking change. Nothing was added to ActiveRecord::Base to make this work.
I did the same thing on a Rails 4.2.6 project because the models used UUIDs for ids instead of integers, and this required a change to the default ORDER BY. So, instead of using copy-paste or a concern, I went with inheritance using a UuidModel class and self.abstract_class = true.
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.
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.
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
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).