Adding code to a model with a custom rake task - ruby-on-rails

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.

Related

How to do ActiveModel out of box?

Ok, I was poking around github's rails activemodel stuff for the heck of it.
Link: https://github.com/rails/rails/tree/master/activemodel
When I saw this code included in their README.rdoc
class Person
include ActiveModel::AttributeMethods
attribute_method_prefix 'clear_'
define_attribute_methods :name, :age
attr_accessor :name, :age
def clear_attribute(attr)
send("#{attr}=", nil)
end
end
person = Person.new
person.clear_name
person.clear_age
Thought that above code was pretty cool as I wasn't familiar with this. Only have seen something like this with dynamic find method, sort of.
So wanted to run it, but I don't know how to run it outside of the context of ROR framework.
How do I go about doing that?
Thanks.
If this question is dumb, let me know? (nicely, please).
Asking because I've heard that people run ORM outside of ROR framework, so, that's why I'm asking this question in the first place. Let me know if I misunderstood this.
You could always run it in an IRB as it's a ruby method not specific to Rails.
http://ruby-doc.org/core-2.1.1/Object.html#method-i-sendirb
For doing Active Record Like Models you could use this gem http://rubygems.org/gems/activemodel, or this gem https://github.com/solnic/virtus.
First create one dummy application in Rails,then open rails console using this command rails c and you past the code then you could verify.
or
Inside application lib directory create one file with .rb extension now open console and you can verify.
More info click here

Ruby on Rails Monkey Patching a Gem's Model

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.

Rails 3 Plugin - Active Record table_name_prefix

Im having problem to use table_name_prefix on my projects. I have a main apllication in my project that have others applications as plugins, these plugins works like a subsystem from the main application.
To organize the tables on database of the subsystems I would like to use the table_name_prefix of the ActiveRecord Plugin.
If I put on init.rb of plugin the command config.active_record.table_name_prefix = "per_" the main application will not work because the ActiveRecord will try to find for "per_users" but the only thing I want is that only the Plugin on my main application use the prefix "per_".
I tried to create a rails folder at my plugin with the command above but the same problem occurs, all the application try to find for prefixed table name.
An alternative is use the set_table_name in the model of plugin, but its not good for me because Im developing subsystems as rails plugin and I dont want to change the models when put the subsystem at the main application.
Someone can help me?
To have each plugin with own prefix, for Rails 3, try organize Your models inside plugin in namespace:
class Foo::Bar < ActiveRecord::Base
...
end
module Foo
def self.table_name_prefix
'foo_'
end
end
This will works just inside the plugin without changing anything inside the main application.
Other approach is to use some main model and inherit it from others like that:
class Foo < ActiveRecord::Base
def self.table_name_prefix
'foo_'
end
end
class Bar < Foo
...
end
sometimes this approach is used to extend all models with extra features.
More information in Rails documentation here
Take a look at this question. I ran into the same problem, forgot my application's name (main module) had the same name as a namespace for my models.

Is it a bad idea do divide the models into directories?

I have a over 100 models in my rails application, and just for organization, I'm dividing them into folders, all still under the main model folder, just to make it simpler to navigate on the project and see files that are related.
Is this a bad idea? What is the rails way to do this?
No, it's not a bad idea. Many people do it and I couldn't live without it in large applications.
There are two ways of doing it:
The first is to just move your models. You will, however, have to tell Rails to load the wayward models (as it won't know where they are). Something like this should do the trick:
# In config/application.rb
module YourApp
class Application < Rails::Application
# Other config options
config.autoload_paths << Dir["#{Rails.root}/app/models/*"]
end
end
The first way is easy, but is not really the best way. The second way involves namespacing your models with groups they're in. This means that instead of having User and UserGroup and UserPermissions, you have User, User::Group and User::Permission.
To use this, generate a model like this: rails generate model User::Group. Rails will automatically create all of the folders for you. An added benefit is that with this approach, you won't have to spell out the full model name for associations within a namespace:
class User < ActiveRecord::Base
belongs_to :group # Rails will detect User::Group as it's in the same namespace
end
class User::Group < ActiveRecord::Base
has_many :users
end
You can specify however many levels of namespacing as you want, so User::Group::Permission would be possible.
For 100 models, it's practically a requirement. 100 models is noisy in one directory.
Try this to get an idea of the Rails Way (tm)
rails new MultiDirectoryExample
cd MultiDirectoryExample
rails generate scaffold User::Photo description:string
Watch the script output and view the generated files.

How/When/Where to Extend Gem Classes (via class_eval and Modules) in Rails 3?

What is the recommended way to extend class behavior, via class_eval and modules (not by inheritance) if I want to extend a class buried in a Gem from a Rails 3 app?
An example is this:
I want to add the ability to create permalinks for tags and categories (through the ActsAsTaggableOn and ActsAsCategory gems).
They have defined Tag and Category models.
I want to basically do this:
Category.class_eval do
has_friendly_id :title
end
Tag.class_eval do
has_friendly_id :title
end
Even if there are other ways of adding this functionality that might be specific to the gem, what is the recommended way to add behavior to classes in a Rails 3 application like this?
I have a few other gems I've created that I want to do this to, such as a Configuration model and an Asset model. I would like to be able to add create an app/models/configuration.rb model class to my app, and it would act as if I just did class_eval.
Anyways, how is this supposed to work? I can't find anything that covers this from any of the current Rails 3 blogs/docs/gists.
I do this as follows, first add a file to config/initializers where you can require the files that contain your extensions:
# config/initializers/extensions.rb
require "#{Rails.root}/app/models/category.rb"
require "#{Rails.root}/app/models/tag.rb"
Then you can just re-open the classes and add whatever else you need:
# app/models/category.rb
class Category
has_friendly_id :title
end
Only downside is that the server has to be restarted for any changes to these files to take effect, not sure if there is a better way that would overcome that.
You can use rails_engine_decorator gem:
https://github.com/atd/rails_engine_decorators
Just add in your Gemfile:
gem 'rails_engine_decorator'
And user class_eval in your decorators:
/app/decorators/models/category_decorator.rb
/app/decorators/models/tag_decorator.rb
It works for me. I hope you find it useful!

Resources