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.
Related
I'm using the gem discarded which adds a scope with_discarded on every model on which it is loaded on. To write some code that works with any model I added with_discarded scope (via a class method) on all models in application_record that does nothing so that then it will get overridden in those models which include the module from the discarded gem.
However, rails now produces a warning:
Creating scope :with_discarded. Overwriting existing method User.with_discarded every time I load a class that overwrites the base method. For completeness that's implemented just as:
def self.with_discarded
all
end
Now everything seems to be working fine, but I'm wondering what the correct way to do this would be and why I'm being warned. Am I supposed to define it as a scope rather than a class method (EDIT: tried it still get the warning)? Alternatively, is there a way to just filter this warning out of all my logs without loading a gem like Semantic Logger. Problem is that this warning shows up every time my GoodJob Scheduler runs and is polluting my logs.
So I followed #tadman's suggestion and just implemented a new method as follows
def self.include_discarded
respond_to?(:with_discarded) ? with_discarded : all
end
This doesn't quite let me overload the original with_discarded scope in the sense that I had to switch all the uses to include_discarded but it has the same effect. You could try and give the method the same name via metaprogramming but then you'll need a good way to run code in every subclass of ApplicationRecord.
I'm using the globalize gem to translate the name attribute of my Color model. The gem seems to generate a Color::Translation ActiveRecord model, but it doesn't provide the file in app/models.
I want to add validations to this model, so I'm wondering if I can just create a file called app/models/color_translations.rb and do something like:
class Color::Translation < ActiveRecord::Base
validates_presence_of :name
end
would this extend the class's functionality (which is what I want) or overwrite everything (unwanted)?
This depends slightly on context. If there is a class Color::Translation then the result of loading your app/models/color_translations.rb file will be to add that validation. However if there is no such class, then it will define a new one.
The tricky thing in development is that classes are (in general) loaded on demand - you don't in general know what is already loaded versus what could be loaded. One way around this is to do
Color::Translation.class_eval do
validates_presence_of :name
end
which will never create a new class - it will use an existing one (if necessary Rails' autoloading will kick in) but if it can't find one it will raise an error.
The second problem you'll have is also related: if you stick this in a file in app/models how will rails know to load it if the class already exists?
It looks like globalizes creates these classes on the fly, so the safest place is to put this at the bottom of color.rb. This also ensures that if rails reloads Color and globalize thus creates a new Color::Translation that you validation will get added to this new class too.
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 have a class method mixed in to all my models. the method gets called when the model class is evaluated. unfortunately (for me), this seems to be on-demand, whenever the model is needed in development env. how can have rails load all the models at start up? is this even advisable?
class Foo < ActiveRecord::Base
include Acl
register_acl # i need this to be called for all models at start up
end
Basically, the register_acl takes a few arguments of "actions" that the model would like to be access controlled. Suppose one of the action of Foo is "manage" and the system needs to be aware of this action at start up. I think in the model is the most natural place to have this logic.
thank you!
The correct way to do this application-wide is to turn on cache_classes in your configuration. By default it's off in development but on in production.
If you want to do it sporadically:
Rails.application.eager_load!
I dont know if this is ideal, but it works for me. Somewhere in the config/initialize/, i do this:
Dir.glob("#{Rails.root}/app/models/*.rb").sort.each { |file| require_dependency file }
and that preloads my models
In MVC concept models are not intended to act by themselves, i.e. they should only act when controller sends them a message (for example, #foo.register_acl). Model instances even should not exist until they are created by controller.
What are you trying to achieve with your register_acl method?
If you really need something to be executed on object creation you can use initialize() method which is called whenever a Ruby object is created.
However if you need model to execute some code by itself you are most likely facing some code smell and you need to change something within your app.
I'm making a forum application with various levels of authorization, one of which is a Monitor. I am doing this by extending my User class, and I plan on fine tuning this with "-ship" classes (e.g. administratorship, authorship, moderatorship, etc.). Apparently the Monitor class is part of ruby mixin. How do I keep my resource name without the collisions?
Some possibilities:
avoid the require 'monitor.rb' call which is pulling in the standard Monitor instance
do some runtime magic to rename the existing Monitor class.
monkey with your load path so that require 'monitor.rb' pulls in an empty implementation of Monitor.
But in all cases you could end up with the situation where a 3rd party library is using Monitor expecting it to be the standard Monitor class. So, I'd advise against any of the above.
I'd say your only two reasonable options are:
A) you could put your class in a namespace:
Module MyApp
class Monitor
#...
end
end
if your app uses some kind of auto-require magic (e.g it's a rails app) then you would put your implementation in /my_app/monitor.rb. When you wanted to refer to that class you would do something like my_monitor = MyApp::Monitor.new(), or whatever.
B) you could use a different class name :)
Declare your Monitor class in other module.
module MyModule
class Monitor
end
end
FYI, I just found a neat trick (errr, hack) to get around this which may work.
I work on a large legacy application which, unfortunately, has a "Fixture" model which is quite important and which is used everywhere. When running tests, it's impossible to create a Fixture instance because of the Fixture class used by ActiveRecord when running tests. So I did the following:
FixtureModel = Fixture.dup
This freezes my class in place so that I can refer to it later (but just in my tests!) without being extended by the ActiveRecord Fixture class (which is not namespaced)