Ruby on Rails Monkey Patching a Gem's Model - ruby-on-rails

This might be silly, but I'm including a gem which represents all the models I need for my project. I want to add a method, to_custom_string to one of those models, Person.
I was attempting to do this via (following this example):
config/initializers/extensions/person.rb
Which contained something like:
class Person < ActiveRecord::Base
def to_custom_string
address.street.to_s
end
end
The Person class in the gem has a has_one :address association.
The problem I was experiencing was that this patch seems to override the Person class from the gem, instead of patching it. What's crazy is that this override behavior was only experienced via rake (all of the associations declared in the Person class from the gem are lost).
My rake task was something like:
namespace :convert
task :all_persons => :environment do
Person.where(:param => value).includes(:address).find_in_batches(:batch_size => 2000) do |persons|
persons.each do |person|
puts person.to_custom_string
end
end
end
end
calling bundle exec rake convert:all_persons gave me:
Association named 'address' was not found; perhaps you misspelled it?
But copying and pasting the code in the rake task into rails console worked fine.
My current solution is to copy the code for Person from the gem into my app/models directory, and have my to_custom_string method there, which I know is wrong.
Can someone please explain why a) irb preserved my Person associations, but rake did not, and b) how I can get rake to cooperate?
Thank you!

First of all instead of reopening the class I would create a Module and include it into the Person. So it would look like that
module CustomString
def to_custom_string
address.street.to_s
end
end
Person.send(:include, CustomString)
Also it seems like the Person model is not yet available at the point of running the initializer. You may want to put this in your application.rb if still doesn't work.
config.railties_order = [ModelEngine::Engine, :main_app, :all]
I guess the reason why it works in irb and not in rake is because they look up classes differently. Irb (which I believe you run by running rails console) loads all the classes at once therefore it loads the classes from engine, then it runs the initializer where you have the classes from engine already defined. I guess (though I'm not sure) Rake in development mode uses lazy loading of constants. So it doesn't load all the classes at the very beginning and only when it finds a constants that is undefined. Then it starts looking for a file that may define that constant. Since you put some Person in initializer it doesn't look up the engine's model at all cause at the point it sees Person it has the Person definition already. That's why the inclusion of module instead of reopening the class may help -> it enforces that it will lookup the Person constant from engine.

I think it will work as long as you just reopen the class, without inheriting from ActiveRecord::Base again. So, like this:
class Person
def custom_string
address.to_street.to_s
end
end
Edit:
You might also need to add a line like this before you reopen the class:
require_dependency ModelEngine::Engine.root.join('app', 'models', 'person').to_s
where ModelEngine::Engine is just the class for the engine that contains all your models.

Related

Rails , Model created inside a namespace cannot be accessed

I have created a model named "RandomStuff" inside namespace "Module".. but when i try Module::RandomStuff in rails console. it gives the following error
Module::RandomStuff(Table doesn't exist)
and cant access it anywhere in my rails app. when i run ActiveRecord::Base.connection.tables in irb the table "module_random_stuffs" is listed there..
Here are the steps i followed , Let me know if i am missing something
rails generate model Module::RandomStuff
then added one column in the migration file
class CreateModuleRandomStuffs < ActiveRecord::Migration
def change
create_table :module_random_stuffs do |t|
t.string :test_column
t.timestamps null: false
end
end
end
and then ran the following command
rake db:migrate
Still i am unable to access the model any where..
Thanks #DVG for pointing it out.
The following line was missing in my model Modul::RandomStuff
self.table_name = 'module_random_stuffs'
I just love these situations.
Sometimes, when Rails fails to load something it acts as if that something never existed. You can run into this issue if, for some reason, some constant (class or module at least, I've seen with both) had a mistake in its definition, but since it was optional, no error was thrown.
I actually ran into the same issue when using active_model_serializers. It tries to infer a serializer class from model's name. If it fails to find a matching one, it falls back to "serializerless" rendering. That confused me for a second, I was pretty sure I defined it, even in the proper file.
In your specific case Rails has to define a module called Module in order to specify a common table prefix there. Like so:
module Module
def self.table_name_prefix
'module_'
end
end
But try defining a module Module in irb and Ruby will refuse:
module Module
end
# TypeError: Module is not a module
It's not a module? Huh. Then what is it?
Module.class
# => Class
A class? That's right, moreover, it's a Ruby core class. So just choose a different module name. What you've chosen collides with something really essential.
Hacky mode (never actually do this, otherwise... wat)
You can fix that by altering the generated module.rb, replacing module with class. That will reopen the Module class and define your model inside it, making things work as expected. But technically, this is an absolutely unnecessary monkeypatch.

Rails, Custom Folders and Namespaces

I'm running Rails 3.2.7,
I have a folder '/app/jobs'
and the following in my 'config/application.rb' file
config.autoload_paths += %W(#{Rails.root}/app/jobs)
And everything is okay.
However if I want to namespace my classes eg
class Jobs::UpdateGameStatus
#methods etc
end
Rather than
class UpdateGameStatus
#methods etc
end
Then I get
uninitialized constant Jobs (NameError)
It's not the end of the world but I'd love to know why...
I fixed it in the end, wrapping all my classes with a Jobs module was what I needed to do.
my files were located in 'app/jobs'
and looked like this
module Jobs
class JobName
#methods etc
end
end
and are used like so
Jobs::JobName.method(args)
I know you have already sorted this out, and this is old, but in ruby, it is also possible to declare the namespaced class directly using class Jobs::JobName. It's a little less typing, and achieves the same result.
Edit: As #D-side pointed out, Jobs has to already be defined. My own code that uses this is based around STI, which presumes that the previous class/module I am extending already exists.

new to rails. going crazy when just trying to add a class and use it

hi i know i am new to rails.
i came from ASP.Net mvc
but although most of the stuff in rails are very easy to do sometimes the small things which are easy in .net makes you crazy in rails.
i have a rails app and im just trying to add a class. and use it in my controller. this class is just for holding data. not from the db. just a simple class for me to use
so i added the class file first in the "/libs/assests" folder. then i read i needed to add a line to the application.rb file that says to load the files from there
so i did..
config.autoload_paths += Dir["#{config.root}/lib", "#{config.root}/lib/**/"]
this still didn't work..
so ive put my class file in the regular Models folder. but it seem its still isn't working
this is my class code:
class Person
attr_accessor :name, :role
def initialize(name, role)
#name = name
#role = role
end
end
and in one of my controller is try to do this:
Person.new("name", "worker");
but i get this error:
uninitialized constant MainController::Person
what is the big deal?.. why is this so complicated to add a public class to a project?
thanks
You have to require the .rb file where the class is specified, you can do that with "require" or "require_relative":
http://rubylearning.com/satishtalim/including_other_files_in_ruby.html
In your Rails.root start up the console:
rails c
Just reference the class name:
Person
What do you see?
Without know much more, it looks like your load path might not be right. See what's in there:
puts $:.join("\n")
Lastly, brute forcing it might give you more info about the problem:
require Rails.root.join("app","models", "person")
This loads the file manually and skips the rails auto loading magic.

Loading ActiveRecord models in the proper order outside of a rails app

How do I load/require my activerecord models in the proper order outside of a rails app. I have many STI models and I am getting an uninitialized constant exception.
$:.push File.expand_path("../../../app/models", __FILE__)
require "active_record"
Dir["#{File.expand_path('../../../app/models', __FILE__)}/*.rb"].each do |path|
require "#{File.basename(path, '.rb')}"
end
I have a lot of jobs that I need to run with resque and I would rather not have my rails app load everytime and be deployed to all of the worker machines
EDIT: One point to clarify as well. There are two projects a Rails project and a project that is a rails engine which contains my models. I dont load the rails engine itself with my resque jobs I just use the snippet above in a separate class to load active record on the models. This always worked until I added some STI models which because of the naming caused the children to attempt to be loaded before the parent. The rails engine project loads just fine in the rails project no issues there this is just because I am trying to use active record outside of a rails project.
A very simple solution if you don't want to autoload is to require the base class in the children classes. Explicitly requiring dependencies is a good thing. :)
app/models/profile.rb
class Profile < ActiveRecord::Base
end
app/models/student.rb
require 'models/profile'
class Student < Profile
end
app/models/teacher.rb
require 'models/profile'
class Teacher < Profile
end
Models will be autoloaded on their first mention. So just name them somewhere in a proper order (say, in config/initializers/load_order.rb):
Product
LineItem
Cart
and check if it helps.
I fixed my issue. There may be a better way but this does it for me.
basedir = File.expand_path('../../../app/models', __FILE__)
Dir["#{basedir}/*.rb"].each do |path|
name = "#{File.basename(path, '.rb')}"
autoload name.classify.to_sym, "#{basedir}/#{name}"
end

Adding code to a model with a custom rake task

I have written a simple blog plugin (it's actually a rails engine). It is designed to be installed into an app that already has a user model set up.
In order to save me from having to open up my User model and manually inserting "has_many :posts", I'd like to write a rake task that automatically does this for me.
If I were to package my engine as a generator inside a gem, then the following would probably work:
def manifest
record do |m|
m.insert_into "app/models/user.rb", 'has_many :posts'
end
end
Can this kind of thing be done with from a rake task? I've look around and I can't find an answer... thanks in advance
Can you include a model file in your plugin that would open up the User class and add the "has_many :posts"?
class User < ActiveRecord::Base
has_many :posts
end
I think that would work because you can open Ruby classes at any time and from any file; so no matter if the project using your plugin has a user.rb file in his model folder, you file will also be loaded and the has_many will be added to the User class at runtime.
Hope it helps.
You can definitely access your model through a rake task. You have to be sure to pass it your environment though so that it knows about your models. For example,
desc"This will insert the Posts"
task(:insertPosts => :environment) do
#your code here
end
Is this a task where actually modifying the source is appropriate? Have you considered including a module? Please give further details of what you're trying to achieve for correct guidance.

Resources