Is there any way to access ApplicationRecord and its models inside an initialization script? Like:
# config/initializers/start.rb
include ApplicationRecord
User.all.each do |user|
# do stuff with users
end
There is no guarantee your initializer in config/initializers will be executed after all gems' initializers, so if your initializer relies on ActiveRecord fully initialized (or any other moving part of the application) you'd better put the code into config.after_initialize block. The documentation is quite explicit with regards to that.
Thanks to #Konstantin Strukov, that's how I ended up doing
# config/initializers/start.rb
Rails.application.config.after_initialize do
User.all.each do |user|
# do stuff with users
end
end
Related
I would like to setup a before_create for all of my modules
what i have been trying is:
module ActiveRecord
module UserMonitor
require 'securerandom'
before_create :attach_uuid
def attach_uuid
self.uuid = SecureRandom.uuid.gsub("-","")
end
end
end
This does not seem to be working.
if i go into each module and add it in there it works, but i want to do it on a global scale.
Any thoughts or ideas on how i can achieve this in this manner? i know i could do it in triggers and such but i don't want to go that route and i would like to avoid hitting every module/class in case i need to change something.
Currently using Ruby 1.9.3 Can not currently upgrade my app until i make future code changes.
Thanks!
An other solution - I use, is to put the logic for UUID in an own module, that you include. I already have some (class-) methods I add to my AR, like set_default_if, so it was a good place for me.
module MyRecordExt
def self.included base
base.extend ClassMethods # in my case some other stuff
base.before_create :attach_uuid # now add the UUID
end
def attach_uuid
begin
self.uuid = SecureRandom.uuid
rescue
# do the "why dont we have a UUID filed?" here
end
end
# some other things not needed for add_uuid
module ClassMethods
include MySpecialBase # just an eg.
def default_for_if(...)
...
end
end
end
and then
class Articel < ActiveRecord::Base
include MyRecordExt
...
end
In general I avoid doing something for ALL models modifying AR base - I made the first bad experience with adding the UUID to all, and crashed with devise GEMs models ...
If you define attach_uuid in the ActiveRecord module, can't you just call the before_create :attach_uuid at the top of each controller? This is DRY.
Is there a UserMonitor controller that you could add it to?
class UserMonitor < ActiveRecord::Base
before_create :attach_uuid
end
This extension create cache_find method for all models of app (I've create this using this post).
config/active_record_extension.rb
require 'active_support/concern'
module ActiveRecordExtension
extend ActiveSupport::Concern
# add your instance methods here
def flush_find
Rails.cache.delete([self.class.name, :cached_find, id])
end
included do
after_commit :flush_find
end
module ClassMethods
def cached_find id
Rails.cache.fetch([self.name, :cached_find, id]) { self.find(id) }
end
end
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecordExtension)
I turned this code into a gem and added to this repo.
So I want to add this methods dynamically, something like this:
class User << ActiveRecord::Base
# id, name, email, age...
cached :find, :find_by_name, :find_by_email
end
and the above code should generate cached_find, flush_find, cached_find_by_name, flush_find_by_name... You get it.
I need help to:
Test Rails.cache methods in model_caching gem.
Create code to dynamically add methods to app models based on cached method arguments.
Some links that helped me but do not meet all:
https://github.com/radar/guides/blob/master/extending-active-record.md
http://railscasts.com/episodes/245-new-gem-with-bundler
http://guides.rubyonrails.org/plugins.html
Fell free to clone and improve gem code.
You don't have to hack ActiveRecord::Base. You can add what Marc-Alexandre said right into your concern, like so:
module ActiveRecordExtension
extend ActiveSupport::Concern
...
module ClassMethods
def cached(*args)
define_method "cached_#{arg.to_s}" do
# do whatever you want to do inside cached_xx
end
define_method "flush_#{arg.to_s}" do
# do whatever you want to to inside flush_xx
end
end
end
end
Also, I would not auto include the extension directly in ActiveRecord, I think it's better to explicitly include it in the models you are going to use it.
To add code dynamically you need to hack the ActiveRecord::Base class. In another file (you usually put in lib/core_ext) you could do as follow :
ActiveRecord::Base.class_eval do
def self.cached(*args)
args.each do |arg|
define_method "cached_#{arg.to_s}" do
# do whatever you want to do inside cached_xx
end
define_method "flush_#{arg.to_s}" do
# do whatever you want to to inside flush_xx
end
end
end
end
What it does basically is takes all your arguments for cached (:find, :find_by_name, etc) and define the two methods (cache_find, cache_find_by_name) and flush_find, .. etc)
Hope this helps !
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.
I have around 40 models in my RoR application. I want to setup a after_save callback for all models. One way is to add it to all models. Since this callback has the same code to run, is there a way to define it globally once so that it gets invoked for all models.
I tried this with no luck:
class ActiveRecord::Base
after_save :do_something
def do_something
# ....
end
end
Same code works if I do it in individual models.
Thanks,
Imran
You should use observers for this:
class AuditObserver < ActiveRecord::Observer
observe ActiveRecord::Base.send(:subclasses)
def after_save(record)
AuditTrail.new(record, "UPDATED")
end
end
In order to activate an observer, list it in the config.active_record.observers configuration setting in your config/application.rb file.
config.active_record.observers = :audit_observer
Note
In Rails 4, the observer feature is removed from core. Use the https://github.com/rails/rails-observers gem.
I'm pretty late on this one, but in case someone else is using Rails 3 and finds this, then this response might help.
Some models might not be loaded when the observer is loaded. The documentation says that you can override observed_classes, and that way you can get the subclasses of active record dynamically.
class AuditObserver < ActiveRecord::Observer
def self.observed_classes
ActiveRecord::Base.send(:subclasses)
end
end
This seemed to work for me:
ActiveRecord::Base.after_save do
...
end
Is there a problem I'm not seeing?
Based on #harish's answer and in this answer (https://stackoverflow.com/a/10712838/2226338):
class AuditObserver < ActiveRecord::Observer
Rails.application.eager_load!
observe ActiveRecord::Base.descendants
def after_save(record)
...
end
end
This actually works pretty well for me in 2.3.8:
class AudiObserver < ActiveRecord::Observer
observe :'ActiveRecord::Base'
#
# observe methods...
#
end
I'm working with an external framework (redmine) which has one Project model that has_many EnabledModules.
Projects can have EnabledModules "attached" or "removed" via the module names, like this:
class Project < ActiveRecord::Base
...
has_many :enabled_modules, :dependent => :delete_all
...
def enabled_module_names=(module_names)
enabled_modules.clear
module_names = [] unless module_names && module_names.is_a?(Array)
module_names.each do |name|
enabled_modules << EnabledModule.new(:name => name.to_s)
end
end
end
I'd like to detect when new modules are attached/removed via callbacks on EnabledModule, and not modify the "original source code" if possible.
I can detect "attachments" like this:
class EnabledModule < ActiveRecord::Base
belongs_to :project
after_create :module_created
def module_created
logger.log("Module attached to project #{self.project_id}")
end
end
I thought that a before_destroy would work for detecting removals, but it will not.
This happens because the enabled_modules.clear call on Project.enabled_module_names=, doesn't invoke 'destroy' on the modules. It just sets their project_id to nil. So I figured I should use a after_update or before_update.
If I use after_update, how can I get the 'previous' project_id?
If I use before_update, how can I differentiate between modules that are 'just updated' and modules whose project_id is going to be reset to nil?
Should I use a totally different approach here?
EDIT: I just found out that I could get the old values with '_was' (i.e. self.project_was). However, collection.clear doesn't seem to trigger update callbacks. Any other solutions?
EDIT 2: Changed title
It looks like revision 2473 onwards of Redmine should solve your problem. See the diffs here:
http://www.redmine.org/projects/redmine/repository/diff/trunk/app/models/project.rb?rev=2473&rev_to=2319
Basically the code has been modified such that removed modules are destroyed rather than deleted, the difference being that model callbacks are not fired for deletes.
There's another related fix in revision 3036 that seems important (see http://www.redmine.org/issues/4200) so you might want to pick up at least that version.
I ended up reimplementing the enabled_module_names= method of projects, including a file in vendor/plugins/my_plugin/lib/my_plugin/patches/project_patch.rb and alias.
module MyPlugin
module Patches
module ProjectPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.extend(ClassMethods)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
# This replaces the existing version of enabled_module_names with a new one
# It is needed because we need the "destroy" callbacks to be fired,
# and only on the erased modules (not all of them - the default
# implementation starts by wiping them out in v0.8'ish)
alias_method :enabled_module_names=, :sympa_enabled_module_names=
end
end
module ClassMethods
end
module InstanceMethods
# Redefine enabled_module_names so it invokes
# mod.destroy on disconnected modules
def sympa_enabled_module_names=(module_names)
module_names = [] unless module_names and module_names.is_a?(Array)
module_names = module_names.collect(&:to_s)
# remove disabled modules
enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
# detect the modules that are new, and create those only
module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name) }
end
end
end
end
end
I had to include some code on my vendor/plugins/my_plugin/init.rb file, too:
require 'redmine'
require 'dispatcher'
# you can add additional lines here for patching users, memberships, etc...
Dispatcher.to_prepare :redmine_sympa do
require_dependency 'project'
require_dependency 'enabled_module'
Project.send(:include, RedmineSympa::Patches::ProjectPatch)
EnabledModule.send(:include, RedmineSympa::Patches::EnabledModulePatch)
end
Redmine::Plugin.register :redmine_sympa do
# ... usual redmine plugin init stuff
end
After this, I was able to detect deletions on enabled modules (via before_delete) on my patcher.
Regarding:
If I use after_update, how can I get the 'previous' project_id
Maybe try project_id_was, it's provided by ActiveRecord::Dirty