Class alias in Ruby - ruby-on-rails

I am developing a new Rails app based on a similar existing one. In my old app, I have Coupon class, which is very similar to Ticket in my new app. I want to reuse all code in Coupon, but with a new class name.
Since refactoring is cumbersome in Rails, I wonder if there is a way to create alias for a class in Ruby (similar to alias for attributes and methods).

Classes don't have names in Ruby. They are just objects assigned to variables, just like any other object. If you want to refer to a class via a different variable, assign it to a different variable:
Foo = String

in file coupon.rb:
class Coupon
#...
end
# add this line of code to make alias for class names
# option1. if you haven't defined a "Ticket" class:
Ticket = Coupon
# option2. if Ticket has been defined, you have to redefine it:
Object.send :remove_const, "Ticket"
const_set "Ticket", Coupon
"Any referrence that begins with an uppercase letter, including the names of classes and modules, is a constant" -- << metaprogramming ruby>>, page38, Constant section

Anyone coming here looking for how to alias a rails model class to have a new name:
I was able to simply do Foo = Bar, but had to put Foo inside it's own model file so that I wouldn't get a Uninitialized Constant Error. e.g.
# models/foo.rb
Foo = Bar
Also you may find weirdness trying to use the alias in associations like has_many, has_one etc. I've found you can usually get around those by using the root namespace (or appropriate namespace depending on how your models are structured) to make sure Rails is trying to autoload the right constant:
has_many :foo, class_name: '::Foo'

You've got to be careful with this, because if your class undergoes any state change (added functions, changed constants, class variables, etc) the state that your class was in when the alias was instantiated will not reflect the updated changes in your class.
In order to avoid carpal tunnel without sacrificing readability, you can store a lambda in your alias object rather than the actual class. Of course, the lambda contains the class but this assures your alias will call up the latest version of your class.
I put this in my supermanpatches.rb rails initializer (inside of config/initializers/) ‡
LAP = lambda { LosAngelesParcel }
Now you can call this using LAP[] and a freshly minted version of your class will be loaded. (Allowing you to create instances, for example, by l = LAP[].new)
‡ runs once when rails is loaded & then is pervasive through your app, callable anywhere kind of like a global variable but 'read-only', so to speak.

I agree with warhog, more or less - but I would subclass ticket from your coupon class - that way if you need to do any data munging, you can put the code in your ticket class

Related

How do I provide a default class definition from /lib in Rails?

I'm presently using a git subtree to manage a directory of classes/libraries somewhere under /lib. The idea is to share common classes and libraries from that directory between different projects that I manage. In it are default implementations of various classes, such as models, controllers, etc. The desired result is that any application I set up which has this subtree would not HAVE to define or customize the classes that come with default implementations, but can if needed.
For example, I have a default User model, and wish to include it automatically so that there is no need to define one in /app/models. The problem is that if I DO automatically include it, and then additionally define a User class in /app/models (say to add or override a method or some configs), that definition will never be included because the class already exists.
I'm attempting to take advantage of the fact that a class can be defined twice in Ruby, and it'll more or less "merge" the definitions. For example, defining a User class twice in the following way will result in a single User class with methods for both User.foo and User.bar:
class User
def self.foo; 'foo'; end
end
class User
def self.bar; 'bar'; end
end
The best way I've come up with to do this so far is to write code in an initializer like the following (note that the to_underscores method isn't a standard String method, it's my own):
Rails.configuration.to_prepare do
[
User,
# more classes...
].each do |klass|
filepath = Rails.root.join("app/models/#{klass.name.to_underscores}.rb")
require filepath if File.exist? filepath
end
end
I could clean this up a bit perhaps by looping through class definitions which exist in key subdirectories under /lib so I don't have to manually list out class names that can be extended/overridden in this way, but I feel like there's got to be a better way. This list will be pretty long and a little silly to try and manage, not to mention it'll be a lot trickier with classes defined in a directory structure with subdirectories. I'd like something more automatic. Any thoughts??
Why not use inheritance? So your User model inherits default behaviour from DefaultUser
class User < DefaultUser
end
class DefaultUser < ActiveRecord::Base
end
You can include the DefaultX classes by making a gem to import in Gemfile that has all of these default classes defined.

Rails Single Table Inheritance with dynamically defined classes

In using Rails STI (Single Table Inheritance), I have defined a model named Poi (point of interest).
Our app's requirement dictate that subclasses of Poi (like Restaurant, Club, etc) must be created in an Admin::Categories view (where it has a class_name string input field), so that an Admin should be able to create a new subclass at anytime without needing a programmer to open a new ruby file with an empty (useless) subclass and re-deploy the app.
At the same time, IF in the future we want to specify different behavior (either instance/class methods) for a subclass of Poi, we can just create that ruby file, but that should be an option and not mandatory. The same goes for a different form with different fields for that subclass: we just need to setup a partial_name_for_form instance method in that subclass that returns a string with the partial name and the views will render that accordingly. If none is found, the default Poi views are rendered.
Since Rails raises an error if you try to instanciate a new Poi object with 'type' attribute that doesn't match a subclass of Poi (so the subclass MUST be previously defined), we came up with the following solution for dynamically creating the Poi subclasses based on class_name:
An after_create hook in model Category that defines the new class immediately using this code: Object.const_set(category.class_name, Class.new(Poi))
A require_dependency call in model Poi file (because it's in the autoload path) to require the ruby files for the subclasses we have eventually created hardcoded subclasses (only if the file exists):
Category.all.each do |category|
require_dependency category.class_name.underscore if File.exist (File.join("app","models","pois","#{category.class_name.underscore}.rb"))
end
An initializer that defines all remaining classes (by 'remaining' I mean other subclasses that don't still have their own ruby file defining them) using the same code in #1, but checking if Object.const_defined? category.class_name first (because the ones defined by require_dependency don't need to be re-defined).
Even tough all this complexity made we almost regret using STI in the first place, it was working fine - in development.
But in production environment, after the creation of a new category providing class_name, the Class doesn't get defined, because when trying to create a new Poi with that subclass is raising an error uninitialized constant.
I confirmed in Rails console in Production environment that the after_create hook IS working, because the class is being defined there. My wild guess is that because we use Unicorn, this bug could be related to the forking of the application code, but I have no clue how to proceed.
10.5 require_dependency and Initializers
One could think about doing some require_dependency calls in an initializer to make sure certain constants are loaded upfront, for example as an attempt to address the gotcha with STIs.
Problem is, in development mode autoloaded constants are wiped if there is any relevant change in the file system. If that happens then we are in the very same situation the initializer wanted to avoid!
From http://guides.rubyonrails.org/autoloading_and_reloading_constants.html. From what I read here, it seems require_dependency in initializer works differently between environments. I found a similar question here - see if that helps.
I would avoid creating dynamic constants because of a broken model.

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.

Why is my default attribute accessor not being overwritten?

I have an Article model. The database schema has several columns including 'title'. I am trying to provide my own getter in place of the method_missing-provided getter. At present I am trying simply:
class Article < ActiveRecord::Base
def title
"blah"
end
end
but when I reload the view the title field continues to be sourced from the database. (My server environment is development and I'm riding Rails 3.1.0.) Any ideas?
Input helpers don't use the normal accessor if there is a 'before_type_cast' variant, so in your case, it is accessing title_before_type_cast.
Either also define that, or choose another method name altogether. I would advise the latter, because overriding accessors is confusing, even without this complication. Don't fight ActiveRecord, you won't win.

Ruby/Rails: Reopening vs Overwriting a Class

I want to add a method to a Rails model, to be used in testing. If I do this
class Model
def something_new
do_something
end
end
in the Rails console or in a file loaded at run time, Model is overwritten rather than modified. If I put something like v = Model.classbefore the lines above, the new method is successfully added to the existing class. Apparently the reference is needed to signal that an existing class is being re-opened.
On the other hand, one can add a method to, say, Fixnum without having to refer to it first. What is going on here, and what is the usual way to ensure that an existing class is re-opened and modified rather than being overwritten?
Thanks.
It sounds like you're not requiring the class before using it. When you write Model.class and there is no Model class, Rails automagically brings in Model for you. If you just write class Model, it just sees that as a class definition. Just doing require 'model' should work.
Use class_eval, that way you will be reopening the class the right way.
Here's a very good article on reopening classes.
Just as an addition to Chuck's answer here is the quote from Rails docs:
6.1.1 Constants after the class and module Keywords
Ruby performs a lookup for the constant that follows a class or module keyword because it needs to know if the class or module is going to be created or reopened.
If the constant is not defined at that point it is not considered to be a missing constant, autoloading is not triggered.

Resources