Extracting common ActiveRecord code for similar clases - ruby

I have the following classes defined that have a lot of common code with minor variations.
class ThirdPartyComponent < ActiveRecord::Base
belongs_to :prev_version, :class_name => 'ThirdPartyComponent', :foreign_key => 'prev_version_id'
has_one :next_version, :class_name => 'ThirdPartyComponent', :foreign_key => 'prev_version_id'
attr_accessible :name, :version, :installer, :install_script
mount_uploader :installer, ComponentFileUploader
mount_uploader :install_script, ComponentFileUploader
validates :name, :presence => true
validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ }
validates :installer, :presence => true
validates :install_script, :presence => true
validate :increased_version
def increased_version
# Check to ensure that version number is greater than the previous version number for the same component set
unless prev_version.nil?
version > prev_version.version
end
end
def all_previous_versions
prev_versions = all_versions
prev_versions.shift
prev_versions
end
def all_versions
current_version = self
all_versions = [current_version]
while !current_version.prev_version.nil?
all_versions << current_version.prev_version
current_version = current_version.prev_version
end
all_versions
end
end
class RegistryComponent < ActiveRecord::Base
belongs_to :prev_version, :class_name => 'RegistryComponent', :foreign_key => 'prev_version_id'
has_one :next_version, :class_name => 'RegistryComponent', :foreign_key => 'prev_version_id'
attr_accessible :name, :version, :registry_file
mount_uploader :registry_file, ComponentFileUploader
validates :name, :presence => true
validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ }
validates :registry_file, :presence => true
validate :increased_version
def increased_version
# Check to ensure that version number is greater than the previous version number for the same component set
unless prev_version.nil?
version > prev_version.version
end
end
def all_previous_versions
prev_versions = all_versions
prev_versions.shift
prev_versions
end
def all_versions
current_version = self
all_versions = [current_version]
while !current_version.prev_version.nil?
all_versions << current_version.prev_version
current_version = current_version.prev_version
end
all_versions
end
end
I'm also looking at adding some other components in the future, again with very similar functionality.
I want to extract the common code from these classes into a single file (including the ActiveRecord method calls such as validates, etc.) and then just reference them in the concrete classes.
So far I've tried,
inheritance - I created a base class that inherited from ActiveRecord and then each class inherited from the base class. Result: Rails complained that it couldn't find a database table whose name matched the base class.
inheritance - I considered creating the base class as tableless model instead (see http://railscasts.com/episodes/219-active-model) but then I realised that the concrete classes would also be lacking the full ActiveRecord functionality
composition - I tried defining the common code in a module and then using include or extend in the concrete classes to access it as shown below.
module ComponentBase
belongs_to :prev_version, :class_name => self.class.name, :foreign_key => 'prev_version_id'
has_one :next_version, :class_name => self.class.name, :foreign_key => 'prev_version_id'
attr_accessible :name, :version
validates :name, :presence => true
validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ }
validate :increased_version
def increased_version
# Check to ensure that version number is greater than the previous version number for the same component set
unless prev_version.nil?
version > prev_version.version
end
end
def all_previous_versions
prev_versions = all_versions
prev_versions.shift
prev_versions
end
def all_versions
current_version = self
all_versions = [current_version]
while !current_version.prev_version.nil?
all_versions << current_version.prev_version
current_version = current_version.prev_version
end
all_versions
end
end
class RegistryComponent < ActiveRecord::Base
include ComponentBase
attr_accessible :registry_file
mount_uploader :registry_file, ComponentFileUploader
validates :registry_file, :presence => true
end
This resulted in an error that the belongs_to method is not defined for ComponentBase. This looks the most promising solution but is there any way to execute the ActiveRecord class methods within the context of the class that includes them? Alternatively, is there a better way for me to achieve the same aims?

Your first option was actually the best option. Rails uses Single Table Inheritance, which means the data for all of your subclasses is kept in the same table which is why you got the error you did.
What you should do is create a new model called Component and add to it all the fields that are common across all of your components as well as one extra field called type that should be a string field.
Your Component model will then have all the common fields, logic, and validations.
class Component < ActiveRecord::Base
...
end
Then have have each of your compontent classes subclass Component.
class ThirdPartyComponent < Component
...
end

I stumbled across the following answer at Extending a Ruby class with a standalone piece of code and with a bit of experimentation got it to work. So the final code I ended up with was,
module ComponentBase
def self.included(base)
base.class_eval do
belongs_to :prev_version, :class_name => base, :foreign_key => 'prev_version_id'
has_one :next_version, :class_name => base, :foreign_key => 'prev_version_id'
attr_accessible :name, :version
validates :name, :presence => true
validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ }
validate :increased_version
end
end
def increased_version
# Check to ensure that version number is greater than the previous version number for the same component set
unless prev_version.nil?
version > prev_version.version
end
end
def all_previous_versions
prev_versions = all_versions
prev_versions.shift
prev_versions
end
def all_versions
current_version = self
all_versions = [current_version]
while !current_version.prev_version.nil?
all_versions << current_version.prev_version
current_version = current_version.prev_version
end
all_versions
end
end
class RegistryComponent < ActiveRecord::Base
include ComponentBase
attr_accessible :registry_file
mount_uploader :registry_file, ComponentFileUploader
validates :registry_file, :presence => true
end
The solution was to use the included callback which is called each time the module is included somewhere else. Then, call class_eval on the base module to run the methods in the class context (i.e. as class methods). The trickiest part was getting the class name in this context but it simply turned out that I could use base (not entirely sure why this is the case but it works).

The issue is that you need the belongs_to method to run on the class that is including the module, not the module itself.
Check out module#included http://www.ruby-doc.org/core-2.0/Module.html#method-i-included, which will allow you to run code on the module you are including your module into. Remmeber that Module is an ancestor of Class so this works for classes and modules.
In this case you will want to run belongs_to on the class that the module is being included into, so something like the following should act as an example you can work off of:
module ComponentBase
def self.included(mod)
mod.class_eval do
belongs_to :prev_version, :class_name => self.class.name, :foreign_key => 'prev_version_id'
end
end
end

Related

How to avoid to update record in self referenced model due to not refers to itself and also not to be duplicate references

I have self referenced model:
class Component < ActiveRecord::Base
has_and_belongs_to_many :allergens
has_many :cqnames, as: :cqnable
has_many :inclusions
has_many :ingredients, :through => :inclusions
accepts_nested_attributes_for :cqnames, allow_destroy: true
accepts_nested_attributes_for :ingredients
accepts_nested_attributes_for :inclusions, allow_destroy: true
translates :name, :description
validates :name, presence: true, uniqueness: true
def self.get_children(ing)
#tree ||= []
if ing.ingredients.nil?
return #tree
else
#tree << ing.ingredients
get_children(ing.ingredients)
end
end
end
I have to avoid self referencing to the same record and also to the records already referenced when the respecting record is updating.
Experiment with before_validation...:
class Component < ActiveRecord::Base
before_validation :hokus
has_and_belongs_to_many :allergens
has_many :cqnames, as: :cqnable
has_many :inclusions
has_many :ingredients, :through => :inclusions
has_many :inverse_inclusions, :class_name => "Inclusion", :foreign_key => "ingredient_id"
has_many :composites, :through => :inverse_inclusions, :source => :component
accepts_nested_attributes_for :cqnames, allow_destroy: true
accepts_nested_attributes_for :ingredients
accepts_nested_attributes_for :inclusions, allow_destroy: true
translates :name, :description
validates :name, presence: true, uniqueness: true
require 'set'
def self.pokus(ing)
avoid = Set.new([self])
q = true
if ing.ingredients.present?
ing.ingredients.each do |record|
q = false if avoid.include? record
end
end
q
end
def hokus
Component.pokus(self)
end
end
If you want a simpler version that should get the job done then this may do.
require 'set'
def self.get_children(ing)
tree = Set.new
recursive = lambda {|recs|
recs.ingredients.each do |record|
recursive.call(record.ingredients) if tree.add?(record)
end
}
recursive.call(ing.ingredients)
tree.to_a
end
I think this is a bit of a mess. But I'll give you a pointer on how you may build your list without duplicates.
require 'set'
def self.get_children(ing)
tree = Set.new
avoid = Set.new([self])
recursive = lambda {|recs|
recs.ingredients.each do |record|
pass = false
if avoid.include? record
pass = true
else
tree << record
avoid << record
end
recursive.call(record.ingredients) unless pass
end
}
recursive.call(ing.ingredients)
tree.to_a
end
I've done a fare bit of recursive work like this so I believe that will do what you're hoping to do. Although I haven't tested this particular piece of code.
If you'd like to see a depth first record duplication method I've written you can see it in my gem poly_belongs_to . I use a variation on Set I built which basically does the same thing I demonstrated here with the avoid Set. I explain Set in my blog post here Different Collection Types in Ruby

Rails 3.1.rc5 custom validator: allow_nil doesn't work, :if => lambda{} works

I write a custom validator for AR, named InvitationValidator
So this piece of code:
User < ActiveRecord::Base
...
attr_accessor :invitation
attr_accessible :invitation
validates :invitation, :invitation => true, :allow_nil => true
end
doesn't allow nil invitation to get through
And this one does allow nil value to get through:
User < ActiveRecord::Base
...
attr_accessor :invitation
attr_accessible :invitation
validates :invitation, :invitation => true, if => lambda {|u| u.invitation.present?}
end
My validator looks like this:
class InvitationValidator < ActiveModel::Validator
def validate(record)
record.errors.add(:invitation, :invalid) unless Invitation.where(:code => record.invitation).count > 0
end
end
Why setting allow_nil doesn't help?
(I'm using devise by the way)
The value could be "" (not nil).
:if => lambda works fine because "".present? == false.
Try :allow_blank instead.

how to autobuild an associated polymorphic activerecord object in rails 3

class ItemSource < ActiveRecord::Base
belongs_to :product, :polymorphic => true
end
class RandomProduct < ActiveRecord::Base
has_one :item_source, :as => :product, :autosave => true, :dependent => :destroy
end
What I'd like to do is is call:
a = RandomProduct.find(1)
a.item_source
and if item_source doesn't already exist (= nil), then build it automatically (build_item_source).
previously, I did this with alias_chain_method, but that's not supported in Rails 3.
oh, and I also tried this to no avail:
class RandomProduct < ActiveRecord::Base
has_one :item_source, :as => :product, :autosave => true, :dependent => :destroy
module AutoBuildItemSource
def item_source
super || build_item_source
end
end
include AutoBuildItemSource
end
In Rails 3, alias_method_chain (and alias_method, and alias) work fine:
class User < ActiveRecord::Base
has_one :profile, :inverse_of => :user
# This works:
#
# def profile_with_build
# profile_without_build || build_profile
# end
# alias_method_chain :profile, :build
#
# But so does this:
alias profile_without_build profile
def profile
profile_without_build || build_profile
end
end
But there's always accept_nested_attributes_for as an alternative, which calls build when profile_attributes are set. Combine it with delegate (optional) and you won't have to worry if the record exists or not:
class User < ActiveRecord::Base
has_one :profile, :inverse_of => :user
delegate :website, :to => :profile, :allow_nil => true
accepts_nested_attributes_for :profile
end
User.new.profile # => nil
User.new.website # => nil
u = User.new :profile_attributes => { :website => "http://example.com" }
u.profile # => #<Profile id: nil, user_id: nil, website: "http://example.com"...>
If the association is always created, delegation isn't necessary (but may be helpful, anyhow).
(Note: I set :inverse_of to make Profile.validates_presence_of :user work and to generally save queries.)
(Rails 4, FYI)
I personally prefer setting it up with after_initialize
after_initialize :after_initialize
def after_initialize
build_item_source if item_source.nil?
end
This also works well because you can automatically use forms with what would otherwise be an empty association (HAML because it's nicer):
= form_for #product do |f|
= f.fields_for :item_source do |isf|
= isf.label :prop1
= isf.text_field :prop1
If you didn't have the item_source built already, the label and text field wouldn't render at all.
How about creating the item_source when the RandomProduct is created:
class RandomProduct << ActiveRecord::Base
after_create :create_item_source
end
Of course, if you need to pass specific arguments to the item source, you could do something like this, instead:
class RandomProduct << ActiveRecord::Base
after_create :set_up_item_source
protected
def set_up_item_source
create_item_source(
:my => "options",
:go => "here"
)
end
end
Not that you really need a gem for this since it's so simple to do yourself, but here's a gem that makes it even easier to declare an auto-build:
https://github.com/TylerRick/active_record_auto_build_associations

Polymorphic has_many self-referential

I have A number of models (Article, Video, Photo)
Now I am trying to create a related_to association, such that
An article can have many other articles, videos and photos related to it. As can videos and photos.
Heres what I have tried:
module ActsAsRelatable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_relatable
has_many :related_items, :as => :related
has_many :source_items, :as => :source, :class_name => 'RelatedItem'
end
end
end
class RelatedItem < ActiveRecord::Base
belongs_to :source, :polymorphic => true
belongs_to :related, :polymorphic => true
end
Then I have added acts_as_relatable to my three models (Article, Video, Photo) and included the module in ActiveRecord::Base
When trying in ./script/console I get it to add the related items and the ids work correctly however the source_type and related_type are always the same (the object that related_items was called from) I want the related_item to be the other model name.
Any ideas anyone?
I would use the has many polymorphs plugin since it supports double sided polymorphism you can do something like this:
class Relating < ActiveRecord::Base
belongs_to :owner, :polymorphic => true
belongs_to :relative, :polymorphic => true
acts_as_double_polymorphic_join(
:owners => [:articles, :videos, :photos],
:relatives => [:articles, :videos, :photos]
)
end
and don't forget the db migration:
class CreateRelatings < ActiveRecord::Migration
def self.up
create_table :relating do |t|
t.references :owner, :polymorphic => true
t.references :relative, :polymorphic => true
end
end
def self.down
drop_table :relatings
end
end
I don't know if "Relating" is a good name, but you get the idea. Now an article, video and photo can be related to another article, video or photo.

User model test error from inserting into Group table

I'm just starting out with tests. When I run this one:
rake test test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
test "should not save without an email address" do
user = User.new
assert_not user.save
end
end
I get the following error:
1) Error:
UserTest#test_should_not_save_without_an_email_address:
ActiveRecord::StatementInvalid: SQLite3::ConstraintException: NOT NULL constraint failed: groups.name: INSERT INTO "groups" ("created_at", "updated_at", "id") VALUES ('2015-08-11 17:31:07', '2015-08-11 17:31:07', 980190962)
This is user.rb
class User < ActiveRecord::Base
has_many :groups
has_many :user_groups
attr_accessor :password
EMAIL_REGEX = /A[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\z/i
validates :password, :confirmation => true #password_confirmation attr
validates_length_of :password, :in => 6..20, :on => :create
validates :email, :presence => true, :uniqueness => true, :format => EMAIL_REGEX
before_save :encrypt_password
after_save :clear_password
def encrypt_password
if password.present?
self.salt = Digest::SHA1.hexdigest("# We add {self.email} as unique value and #{Time.now} as random value")
self.encrypted_password = Digest::SHA1.hexdigest("Adding #{self.salt} to {password}")
end
end
def clear_password
self.password = nil
end
end
This is test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
As far as I can tell I don't have any callback or otherwise that would attempt to write to the "groups" table. My "groups.yml" is default, but that shouldn't matter if I'm only testing this one model, correct? Any help as to where I could start looking would be much appreciated. Thanks!
test_helper.rb was setting up all my fixtures and they weren't defined. Commenting "fixtures :all" fixed it.

Resources