How do I extend a model in Rails? - ruby-on-rails

I need to extend a model in a Rails 2.3.11 app without touching the original source file. I need to add a :has_many association in it. I've tried the approach mentioned in Extend model in plugin with "has_many" using a module without success. The class I need to extend is called UbiquoUser. Here the code I have in lib/extensions.rb:
module Sindicada
module Extensions
autoload :UbiquoUser, 'extensions/ubiquo_user'
end
end
UbiquoUser.send(:extend, Sindicada::Extensions::UbiquoUser)
Here's what I have in lib/extensions/ubiquo_user.rb:
module Sindicada
module Extensions
module UbiquoUser
module ClassMethods
def has_audio_favorites
has_many :audios, :through => :audios_favorite
end
end #ClassMethods
def self.included(base)
base.extend(ClassMethods).has_audio_favorites
end
end #UbiquoUser
end #Extensions
end #Sindicada
However, when I try to access the property audios of UbiquoUser on the app I get the error undefined method audios for class blablabla.
I also have the require 'extensions' in the environment.rb file and have checked that the files are being loaded.

The problem you have now is that you are extending your class, not including a module into it, so the Sicada::Extensions::UbiquoUser#included method never gets called.
To fix this, change this line:
UbiquoUser.send(:extend, Sindicada::Extensions::UbiquoUser)
to
UbiquoUser.send(:include, Sindicada::Extensions::UbiquoUser)

Related

Circular dependency detected while autoloading constant Concerns::<NameOfConcern>

Note: Before you think of marking this question as a duplicate of an other similar question, do note this thing that this question is being asked about concerns in Rails, while the other questions that I've searched deal with controllers. No question I've found out, that deals with concern.
I've a file named comments_deletion.rb inside app/models/concerns, and it contains the following code:
module CommentsDeletion
extend ActiveSupport::Concern
included do
after_save :delete_comments, if: :soft_deleted?
end
def soft_deleted?
status == 'deleted'
end
def delete_comments
comments.each &:destroy
end
end
And I'm trying to mix the file in my model by writing the following code:
class Employee < ActiveRecord::Base
include CommentsDeletion
# all the other code
end
Just doing this, and then upon invoking rails console, it gives me the following error:
Circular dependency detected while autoloading constant Concerns::CommentsDeletion
I'm using Rails 4.0.2, and this thing has driven me nuts, and I'm unable to figure out what's wrong with my code.
Very strange that the following thing hasn't been mentioned anywhere in Rails documentation, but with it, my code works without any problems.
All you have to do is to replace CommentsDeletion with Concerns::CommentsDeletion. Put otherwise, you have to put Concerns before the name of your module that you would like to mix into your models later on.
Now, that's how my module residing inside concerns directory looks like:
module Concerns::CommentsDeletion
extend ActiveSupport::Concern
included do
after_save :delete_comments, if: :soft_deleted?
end
def soft_deleted?
status == 'deleted'
end
def delete_comments
comments.each &:destroy
end
end
In my case, my code likes like:
#models/user.rb
class User < ApplicationRecord
include User::AuditLog
end
and
#model/concern/user/audit_log.rb
module User::AuditLog
extend ActiveSupport::Concern
end
it works fine in development environment, but in production it got error as title. When I change to this it works fine for me. Rename the folder name in concern if it has the same name with models.
#models/user.rb
class User < ApplicationRecord
include Users::AuditLog
end
and
#model/concern/users/audit_log.rb
module Users::AuditLog
extend ActiveSupport::Concern
end

Rails model directly use other class methods

I am doing Rails app and i see that i could refactor my code. Just can't find how. I have many models like Company, News, Profile and many more which use Image upload. So in every class i must always copy-paste 30 rows of methods which implement always the same logic - upload_image, get_image_name, delete_image. How is possible to do, that my Model class would automatically have the methods from somewhere else? I would like to just put to the Model - load 'GlobalMethods', or even somehow include the methds in activerecord:base to just have them for every class and i would use whenever i want. And from controller i would just leave as it is. For example - News.upload_image and it would do this same as this logic would be in original model.
Please explain with example, because i have readed more and didn't understand or it even possible.
At this moment i did:
#models/concerns/uploading.rb
module Uploading
extend ActiveSupport::Concern
included do
def do_upload
puts 'aaaaaaaaaaaaaaa'
end
end
end
and model:
class Company < ActiveRecord::Base
include Uploading
end
and i get this error:
uninitialized constant Company::Uploading
My rails version: '4.2.5'
I did server restarts after every try
My application.rb file looks:
require File.expand_path('../boot', __FILE__)
require 'rails/all'
Bundler.require(*Rails.groups)
module Vca
class Application < Rails::Application
config.active_record.raise_in_transactional_callbacks = true
config.autoload_paths += %W(
#{config.root}/app/models/concerns/uploading.rb
)
end
end
Still the same
NameError in CompaniesController#index
uninitialized constant Company::Uploading
class Company < ActiveRecord::Base
include Uploading # <-- error
end
I have gem 'Spring'. I turned off server, in terminal i runned "spring stop" and started server. But it didn't solve this.
Take a look at ActiveSupport::Concern
For example
app/models/concerns/my_concern.rb
module MyConcern
extend ActiveSupport::Concern
included do
# common stuff here
end
class_methods do
# define class methods here
end
end
app/models/my_model.rb
class MyModel < ActiveRecord::Base
include MyConcern
# ...
end
By the way, you can refer to the model class with self in the included block and out of any instance methods, and inside the methods within the class_methods block.
You'll notice that your standard rails project includes a folder called concerns under your models folder.
Create a module in that folder, image_handling.rb
module ImageHandling
extend ActiveSupport::Concern
included do
# ...(include all your common methods here)
end
end
In your Company, News, Profile models call the concern...
class Company < ActiveReecord::Base
include ImageHandling
...
end

Why do functions from my Rails plugin not work without specifically requiring?

I need some help with my plugin. I want to extend ActiveRecord::Base with a method that initializes another method that can be called in the controller.
It will look like this:
class Article < ActiveRecord::Base
robot_catch :title, :text
...
end
My attempt at extending the ActiveRecord::Base class with robot_catch method looks like following. The function will initialize the specified attributes (in this case :title and :text) in a variable and use class_eval to make the robot? function available for the user to call it in the controller:
module Plugin
module Base
extend ActiveSupport::Concern
module ClassMethods
def robot_catch(*attr)
##robot_params = attr
self.class_eval do
def robot?(params_hash)
# Input is the params hash, and this function
# will check if the some hashed attributes in this hash
# correspond to the attribute values as expected,
# and return true or false.
end
end
end
end
end
end
ActiveRecord::Base.send :include, Plugin::Base
So, in the controller, this could be done:
class ArticlesController < ApplicationController
...
def create
#article = Article.new(params[:article])
if #article.robot? params
# Do not save this in database, but render
# the page as if it would have succeeded
...
end
end
end
My question is whether if I am right that robot_catch is class method. This function is to be called inside a model, as shown above. I wonder if I am extending the ActiveRecord::Base the right way. The robot? function is an instance method without any doubt.
I am using Rails 3.2.22 and I installed this plugin as a gem in another project where I want to use this functionality.
Right now, it only works if I specifically require the gem in the model. However, I want it the functionality to be included as a part of ActiveRecord::Base without requiring it, otherwise I'd have to require it in every model I want to use it, not particularly DRY. Shouldn't the gem be automatically loaded into the project on Rails start-up?
EDIT: Maybe callbacks (http://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html) would be a solution to this problem, but I do not know how to use it. It seems a bit obscure.
First, I would suggest you make sure that none of the many many built in Rails validators meet your needs.
Then if that's the case, what you actually want is a custom validator.
Building a custom validator is not as simple as it might seem, the basic class you'll build will have this structure:
class SpecialValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# Fill this with your validation logic
# Add to record.errors if validation fails
end
end
Then in your model:
class Article < ActiveRecord::Base
validates :title, :text, special: true
end
I would strongly suggest making sure what you want is not already built, chances are it is. Then use resources like this or ruby guides resources to continue going down the custom validator route.
Answer
I found out the solution myself. Bundler will not autoload dependencies from a gemspec that my project uses, so I had to require all third party gems in an engine.rb file in the lib/ directory of my app in order to load the gems. Now everything is working as it should.
Second: the robot_catch method is a class method.

Include Railtie initialization in seeds.rb in Rails 3.1

I have created a simple railtie, adding a bunch of stuff to ActiveRecord:
0 module Searchable
1 class Railtie < Rails::Railtie
2 initializer 'searchable.model_additions' do
3 ActiveSupport.on_load :active_record do
4 extend ModelAdditions
5 end
6 end
7 end
8 end
I require this file (in /lib) by adding the following line to config/environment.rb before the application is called:
require 'searchable'
This works great with my application and there are no major problems.
I have however encountered a problem with rake db:seed.
In my seeds.rb file, I read data in from a csv and populate the database. The problem I am having is that the additions I made to ActiveRecord don't get loaded, and seeds fails with a method_missing error. I am not calling these methods, but I assume that since seeds.rb loads the models, it tries to call some of the methods and that's why it fails.
Can anyone tell me a better place to put the require so that it will be included every time ActiveRecord is loaded (not just when the full application is loaded)? I would prefer to keep the code outside of my models, as it is code shared between most of my models and I want to keep them clean and DRY.
Putting the extend there just adds it to ActiveRecord::Base.
When a model class is referenced, via Rails 3.1 autoloading/constant lookup, it will load the class. At that point, it is pure Ruby (nothing magic) as to what happens, basically. So I think you have at least a few options. The "bad" option that kind of does what you want it to hook into dependency loading. Maybe something like:
module ActiveSupport
module Dependencies
alias_method(:load_missing_constant_renamed_my_app_name_here, :load_missing_constant)
undef_method(:load_missing_constant)
def load_missing_constant(from_mod, const_name)
# your include here if const_name = 'ModelName'
# perhaps you could list the app/models directory, put that in an Array, and do some_array.include?(const_name)
load_missing_constant_renamed_my_app_name_here(from_mod, const_name)
end
end
end
Another way to do it would be to use a Railtie like you were doing and add a class method to ActiveRecord::Base that then includes stuff, like:
module MyModule
class Railtie < Rails::Railtie
initializer "my_name.active_record" do
ActiveSupport.on_load(:active_record) do
# ActiveRecord::Base gets new behavior
include ::MyModule::Something # where you add behavior. consider using an ActiveSupport::Concern
end
end
end
end
If using an ActiveSupport::Concern:
module MyModule
module Something
extend ActiveSupport::Concern
included do
# this area is basically for anything other than class and instance methods
# add class_attribute's, etc.
end
module ClassMethods
# class method definitions go here
def include_some_goodness_in_the_model
# include or extend a module
end
end
# instance method definitions go here
end
end
Then in each model:
class MyModel < ActiveRecord::Base
include_some_goodness_in_the_model
#...
end
However, that isn't much better than just doing an include in each model, which is what I'd recommend.

Trying to understand how a Controller uses a Plugin/Module

Ok so I am using some module/lib/plugin (not sure of the exact name), let's say its a authentication/authorization plugin like: http://github.com/technoweenie/restful-authentication
class HomeController < ApplicationController
some_module_name_here
end
Now just from adding the code above 'some_module_name_here', I can access to methods from that module.
What is that line of code doing that gives me access to methods/objects from the module?
Is that declaring a variable like in say java/c#:
public SomeModule _someModule;
I know that plugins/modules basically extend the class under the covers, but how does it do this with a single line of code?
Is it called in the constructor somehow?
When you create a ruby plugin, and load it into the rails app via environment.rb, bundler, or a require call, the methods are loaded as "modules" that can be called. The ones that act like you're talking about will have an extra method called acts_as_list or something similar. All that method does is include the methods of the module into the class where that line was called.
Here's an example, which you could include in your app's lib folder and play with:
module Bellmyer
module Pointless
def self.included(base)
base.extend PointlessMethods
end
module PointlessMethods
def acts_as_pointless
unless included_modules.include? InstanceMethods
extend ClassMethods
include InstanceMethods
end
end
end
module ClassMethods
def pointless_class?
true
end
end
module InstanceMethods
def pointless_instance?
true
end
end
end
end
The module is available to any ruby class in your app, but the methods don't actually get loaded until you call acts_as_pointless, which then includes and extends your class with the methods listed. Only the acts_as_pointless method is immediately available to the model. This is the standard pattern for an ActiveRecord plugin.
That's not how it works.
When the plugin or gem is loaded it adds a class method to, in this case, ApplicationController named some_module_name. When you call that methods, a bunch of other class and instance methods are included.
Check out your favourite gem or plugin to see how they do it exactly.

Resources