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.
Related
I am newbie in Rails but have quite an extensive amount of experience coding in other language (such as Java). Well, I liked Ruby's metaprogramming.
Usually, when in my way of learning Rails... I see some plug in such as public_activity, or others that can simply do
include Namespace::SomeModel
And that, in the case of public_activity, their SomeModel's method is somehow called on before the record is created. I suspect that before_create is included in SomeModel. So... I begin to experiment but was stuck that, obviously, the before_create become unavailable to the SomeModel class I am having that is located in directory manually auto-loaded on rails s.
What I want to ask if, how can just by including SomeModel, one of its method is called on ActiveRecord create event?
I just downloaded the source code of the code, but well... it will takes time. And, just to anticipate I cannot found the answer; you guys are my better teacher than silent code. So, give me time to answer even for slightest hint. Thanks.
Rails allows to extend the class you're including a module in. Basic techniques are described in http://api.rubyonrails.org/classes/ActiveSupport/Concern.html#method-i-included
This allows to "set up" a module, like
module Foo
extend ActiveSupport::Concern
included do
# Within this area "self" now refers to the class in which the module is included
# Every method you now call is called agains the class
# As such you can now write things like
validates_inclusion_of ...
define_method ...
end
end
It is a quite famous procedure
Module NewModule
extend ActiveSupport::Concern
def self.included(base)
base.after_create :a_method
end
def a_method
# your_code_here
end
end
class A < ActiveRecord::Base
include NewModule
end
with ActiveSupport::Extend you give NewModule's class and instance methods to A accordingly.
The NewModule.included code is executed when NewModule is included to another class.
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 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
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