ActiveRecord::Base Extension not working - ruby-on-rails

I try to use the code below to extend active record to have my order class a bit cleaner. It is however not working.
class ActiveRecord::Base
def self.has_statuses(*status_names)
validates :status,
:presence => true,
:inclusion => { :in => status_names}
status_names.each do |status_name|
scope "all_#{status_name}", where(:status => status_name)
end
status_names.each do |status_name|
define_method "#{status_name}?" do
status == status_name
end
end
end
end
In environment.rb I require the above extension with
require "#{Rails.root}/lib/active_record_extensions.rb"
If I on the other hand creates an abstract base class OrderBase and let's my order class inherit from that instead it is working as anticipated. What do I need to do to make my monkey patch "take"?
EDIT: forgot to mention I get a message saying method missing for has_statuses.
EDIT2: The above code works in console (rails c) but it does not work in test
EDIT3: I changed environment/test.rb to config.cache_classes = false and that seems to do the trick. Obviously have a lot to learn :)

Where do you store that extension code? I've found that extensions to important Rails classes don't work from the "config/initializers/" directory (where I'd prefer to keep them, for tidiness's sake), but do work if I tack the extension code on at the end of "config/environment.rb" (which I don't like at all, 'cause it gets messy really fast). I've never figured out why.
I'm on Rails 2.3.8, so if you're on Rails 3, this might no longer apply...
EDIT:
I forgot the other trick it took to get an ActiveRecord::Base extension working - the methods (class methods, at least - I haven't done it with an instance method) have to be declared in the metaclass (I think that's what it's called...). Here's my working ActiveRecord::Base extension:
ActiveRecord::Base.class_eval do
class << self
def find_with_benchmark(*args)
bg = Time.now
ret = find_without_benchmark(*args)
MyLogger.ar_time += Time.now - bg
MyLogger.ar_count += 1
return ret
end
alias_method_chain :find, :benchmark
end
end

I added the require to the top of my order class
require "#{Rails.root}/lib/active_record_extensions.rb"
class Order < ActiveRecord::Base

Related

Type of base ActiveRecords?

So I'm writing a gem to extend the default Rails classes. I have already got it working for ActiveRecord::Relation, thought I would like it working for the generic class, but I can't identify its class type.
Currently I have:
User.first.posts.test_method
working, as User.first.posts.class.name == "ActiveRecord::Relation"
However, I can't figure out how to get this working:
User.test_method
and User.class.name just returns Class which isn't really helpful.
I've tried to extend into ActiveRecord::Base but with no luck. Any ideas?
Thanks
Try this
class ActiveRecord::Base
class << self
def this_should_work
"it does"
end
end
end
User.this_should_work
=> "it does"

How do you define a class method?

I have a model OutcomeData with controller OutcomeDatas.
In OutcomeData, I have a method as_cleaned_hash that right now, doesn't do a damn thing. Let's just pretend it returns 'hello'
class OutcomeData < ActiveRecord::Base
attr_accessible :key, :outcome_uid, :task_id, :value
belongs_to :task
belongs_to :outcome
def as_cleaned_hash
'hello i am alive'
end
This is the method that as_cleaned_hash is supposed to follow, if it matters:
#outcome_data = OutcomeData.find_all_by_outcome_uid(params[:outcome_uid])
hash = Hash.new
#outcome_data.each do |p|
unless p[:value].blank? || p[:key] == 'raw'
hash[p[:key]] = p[:value]
end
end
This works fine -- right now I'm throwing it into my controller actions, but since it needs to be used throughout my app, I can't let this happen.
So, for whatever reason, I get an undefined method error.
I called OutcomeData.methods to see if the method was even there, and, nope. (see list here: http://pastebin.com/B3y1r2w7)
OutcomeData.respond_to?('as_cleaned_hash') returns false.
There's nothing fancy going on either, so I'm not quite sure what's happening.
Rails 3.2.12 with Ruby 2.0.0-p195
To define a class method, the syntax is
def self.foo
end
You have defined an instance method.

Defining a Rails helper (or non-helper) function for use everywhere, including models

I have a function that does this:
def blank_to_negative(value)
value.is_number? ? value : -1
end
If the value passed is not a number, it converts the value to -1.
I mainly created this function for a certain model, but it doesn't seem appropriate to define this function in any certain model because the scope of applications of this function could obviously extend beyond any one particular model. I'll almost certainly need this function in other models, and probably in views.
What's the most "Rails Way" way to define this function and then use it everywhere, especially in models?
I tried to define it in ApplicationHelper, but it didn't work:
class UserSkill < ActiveRecord::Base
include ApplicationHelper
belongs_to :user
belongs_to :skill
def self.splice_levels(current_proficiency_levels, interest_levels)
Skill.all.reject { |skill| !current_proficiency_levels[skill.id.to_s].is_number? and !interest_levels[skill.id.to_s].is_number? }.collect { |skill| {
:skill_id => skill.id,
:current_proficiency_level => blank_to_negative(current_proficiency_levels[skill.id.to_s]),
:interest_level => blank_to_negative(interest_levels[skill.id.to_s]) }}
end
end
That told me
undefined method `blank_to_negative' for #
I've read that you're "never" supposed to do that kind of thing, anyway, so I'm kind of confused.
if you want to have such a helper method in every class in your project, than you are free to add this as a method to Object or whatever you see fits:
module MyApp
module CoreExtensions
module Object
def blank_to_negative
self.is_number? ? self : -1
end
end
end
end
Object.send :include, MyApp::CoreExtensions::Object
There are a few options:
Monkey-patch the method into ActiveRecord and it will be available across all of your models:
class ActiveRecord::Base
def blank_to_negative(value)
value.is_number? ? value : -1
end
end
Add a "concern" module which you then mix into selected models:
# app/concerns/blank_to_negate.rb
module BlankToNegate
def blank_to_negative(value)
value.is_number? ? value : -1
end
end
# app/models/user_skill.rb
class UserSkill < ActiveRecord::Base
include BlankToNegate
# ...
end
Ruby Datatypes functionality can be extended. They are not sealed. Since you wan to use it in all places why not extend FIXNUM functionality and add a method blank_to_negative to it.
Here's what I ended up doing. I put this code in config/initializers/string_extensions.rb.
class String
def is_number?
true if Float(self) rescue false
end
def negative_if_not_numeric
self.is_number? ? self : -1
end
end
Also, I renamed blank_to_negative to negative_if_not_numeric, since some_string.negative_if_not_numeric makes more sense than some_string.blank_to_negative.

How does Rails method call like "has_one" work?

I am PHP dev and at the moment I am learning Rails (3) and of course - Ruby. I don't want to believe in magic and so I try to understand as much as I can about things that happen "behind" Rails. What I found interesting are the method calls like has_one or belongs_to in ActiveRecord models.
I tried to reproduce that, and came with naive example:
# has_one_test_1.rb
module Foo
class Base
def self.has_one
puts 'Will it work?'
end
end
end
class Model2 < Foo::Base
has_one
end
Just running this file will output "Will it work?", as I expected.
While searching through rails source I found responsible function: def has_one(association_id, options = {}).
How could this be, because it is obviously an instance (?) and not a class method, it should not work.
After some researching I found an example that could be an answer:
# has_one_test_2.rb
module Foo
module Bar
module Baz
def has_one stuff
puts "I CAN HAS #{stuff}?"
end
end
def self.included mod
mod.extend(Baz)
end
end
class Base
include Bar
end
end
class Model < Foo::Base
has_one 'CHEEZBURGER'
end
Now running has_one_test_2.rb file will output I CAN HAS CHEEZBURGER. If I understood this well - first thing that happens is that Base class tries to include Bar module. On the time of this inclusion the self.included method is invoked, which extends Bar module with Baz module (and its instance has_one method). So in the essence has_one method is included (mixed?) into Base class. But still, I don't fully get it. Object#extend adds the method from module but still, I am not sure how to reproduce this behaviour using extend. So my questions are:
What exactly happened here. I mean, still don't know how has_one method become class method? Which part exactly caused it?
This possibility to make this method calls (which looks like configuration) is really cool. Is there an alternative or simpler way to achieve this?
You can extend and include a module.
extend adds the methods from the module as class methods
A simpler implementation of your example:
module Bar
def has_one stuff
puts "I CAN HAS #{stuff}?"
end
end
class Model
extend Bar
has_one 'CHEEZBURGER'
end
include adds the methods from the module as instance methods
class Model
include Bar
end
Model.new.has_one 'CHEEZBURGER'
Rails uses this to dynamically add methods to your class.
For example you could use define_method:
module Bar
def has_one stuff
define_method(stuff) do
puts "I CAN HAS #{stuff}?"
end
end
end
class Model
extend Bar
has_one 'CHEEZBURGER'
end
Model.new.CHEEZBURGER # => I CAN HAS CHEEZBURGER?
I commend you for refusing to believe in the magic. I highly recommend you get the Metaprogramming Ruby book. I just recently got it and it was triggering epiphanies left and right in mah brainz. It goes over many of these things that people commonly refer to as 'magic'. Once it covers them all, it goes over Active Record as an example to show you that you now understand the topics. Best of all, the book reads very easily: it's very digestible and short.
Yehuda went through some alternatives on way to Rails3: http://yehudakatz.com/2009/11/12/better-ruby-idioms/
Moreover, you can use a (usually heavily abused, but sometimes quite useful) method_missing concept:
class Foo
def method_missing(method, *arg)
# Here you are if was some method that wasn't defined before called to an object
puts "method = #{method.inspect}"
puts "args = #{arg.inspect}"
return nil
end
end
Foo.new.abracadabra(1, 2, 'a')
yields
method = :abracadabra
args = [1, 2, "a"]
Generally, this mechanism is quite often used as
def method_missing(method, *arg)
case method
when :has_one
# implement has_one method
when :has_many
# ...
else
raise NoMethodError.new
end
end

How can I avoid running ActiveRecord callbacks?

I have some models that have after_save callbacks. Usually that's fine, but in some situations, like when creating development data, I want to save the models without having the callbacks run. Is there a simple way to do that? Something akin to...
Person#save( :run_callbacks => false )
or
Person#save_without_callbacks
I looked in the Rails docs and didn't find anything. However in my experience the Rails docs don't always tell the whole story.
UPDATE
I found a blog post that explains how you can remove callbacks from a model like this:
Foo.after_save.clear
I couldn't find where that method is documented but it seems to work.
Use update_column (Rails >= v3.1) or update_columns (Rails >= 4.0) to skip callbacks and validations. Also with these methods, updated_at is not updated.
#Rails >= v3.1 only
#person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
#person.update_columns(attributes)
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_column
#2: Skipping callbacks that also works while creating an object
class Person < ActiveRecord::Base
attr_accessor :skip_some_callbacks
before_validation :do_something
after_validation :do_something_else
skip_callback :validation, :before, :do_something, if: :skip_some_callbacks
skip_callback :validation, :after, :do_something_else, if: :skip_some_callbacks
end
person = Person.new(person_params)
person.skip_some_callbacks = true
person.save
UPDATE (2020)
Apparently Rails has always supported :if and :unless options, so above code can be simplified as:
class Person < ActiveRecord::Base
attr_accessor :skip_some_callbacks
before_validation :do_something, unless: :skip_some_callbacks
after_validation :do_something_else, unless: :skip_some_callbacks
end
person = Person.new(person_params)
person.skip_some_callbacks = true
person.save
This solution is Rails 2 only.
I just investigated this and I think I have a solution. There are two ActiveRecord private methods that you can use:
update_without_callbacks
create_without_callbacks
You're going to have to use send to call these methods. examples:
p = Person.new(:name => 'foo')
p.send(:create_without_callbacks)
p = Person.find(1)
p.send(:update_without_callbacks)
This is definitely something that you'll only really want to use in the console or while doing some random tests. Hope this helps!
Updated:
#Vikrant Chaudhary's solution seems better:
#Rails >= v3.1 only
#person.update_column(:some_attribute, 'value')
#Rails >= v4.0 only
#person.update_columns(attributes)
My original answer :
see this link: How to skip ActiveRecord callbacks?
in Rails3,
assume we have a class definition:
class User < ActiveRecord::Base
after_save :generate_nick_name
end
Approach1:
User.send(:create_without_callbacks)
User.send(:update_without_callbacks)
Approach2:
When you want to skip them in your rspec files or whatever, try this:
User.skip_callback(:save, :after, :generate_nick_name)
User.create!()
NOTE: once this is done, if you are not in rspec environment, you should reset the callbacks:
User.set_callback(:save, :after, :generate_nick_name)
works fine for me on rails 3.0.5
If the goal is to simply insert a record without callbacks or validations, and you would like to do it without resorting to additional gems, adding conditional checks, using RAW SQL, or futzing with your exiting code in any way, consider using a "shadow object" pointing to your existing db table. Like so:
class ImportedPerson < ActiveRecord::Base
self.table_name = 'people'
end
This works with every version of Rails, is threadsafe, and completely eliminates all validations and callbacks with no modifications to your existing code. You can just toss that class declaration in right before your actual import, and you should be good to go. Just remember to use your new class to insert the object, like:
ImportedPerson.new( person_attributes )
rails 3:
MyModel.send("_#{symbol}_callbacks") # list
MyModel.reset_callbacks symbol # reset
You could try something like this in your Person model:
after_save :something_cool, :unless => :skip_callbacks
def skip_callbacks
ENV[RAILS_ENV] == 'development' # or something more complicated
end
EDIT: after_save is not a symbol, but that's at least the 1,000th time I've tried to make it one.
You can use update_columns:
User.first.update_columns({:name => "sebastian", :age => 25})
Updates the given attributes of an object, without calling save, hence skipping validations and callbacks.
The only way to prevent all after_save callbacks is to have the first one return false.
Perhaps you could try something like (untested):
class MyModel < ActiveRecord::Base
attr_accessor :skip_after_save
def after_save
return false if #skip_after_save
... blah blah ...
end
end
...
m = MyModel.new # ... etc etc
m.skip_after_save = true
m.save
Looks like one way to handle this in Rails 2.3 (since update_without_callbacks is gone, etc.), would be to use update_all, which is one of the methods that skips callbacks as per section 12 of the Rails Guide to validations and callbacks.
Also, note that if you are doing something in your after_ callback, that does a calculation based on many association (i.e. a has_many assoc, where you also do accepts_nested_attributes_for), you will need to reload the association, in case as part of the save, one of its members was deleted.
The most up-voted answer might seem confusing in some cases.
You can use just a simple if check if you would like to skip a callback, like this:
after_save :set_title, if: -> { !new_record? && self.name_changed? }
with Rails 6 you can now use the insert methods
from the documentation:
Inserts multiple records into the database in a single SQL INSERT
statement. It does not instantiate any models nor does it trigger
Active Record callbacks or validations. Though passed values go
through Active Record's type casting and serialization.
https://gist.github.com/576546
just dump this monkey-patch into config/initializers/skip_callbacks.rb
then
Project.skip_callbacks { #project.save }
or the like.
all credit to the author
A solution that should work across all versions of Rails without the use of a gem or plugin is simply to issue update statements directly. eg
ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}"
This may (or may not) be an option depending on how complex your update is. This works well for eg updating flags on a record from within an after_save callback (without retriggering the callback).
When I need full control over the callback, I create another attribute that is used as a switch. Simple and effective:
Model:
class MyModel < ActiveRecord::Base
before_save :do_stuff, unless: :skip_do_stuff_callback
attr_accessor :skip_do_stuff_callback
def do_stuff
puts 'do stuff callback'
end
end
Test:
m = MyModel.new()
# Fire callbacks
m.save
# Without firing callbacks
m.skip_do_stuff_callback = true
m.save
# Fire callbacks again
m.skip_do_stuff_callback = false
m.save
I needed a solution for Rails 4, so I came up with this:
app/models/concerns/save_without_callbacks.rb
module SaveWithoutCallbacks
def self.included(base)
base.const_set(:WithoutCallbacks,
Class.new(ActiveRecord::Base) do
self.table_name = base.table_name
end
)
end
def save_without_callbacks
new_record? ? create_without_callbacks : update_without_callbacks
end
def create_without_callbacks
plain_model = self.class.const_get(:WithoutCallbacks)
plain_record = plain_model.create(self.attributes)
self.id = plain_record.id
self.created_at = Time.zone.now
self.updated_at = Time.zone.now
#new_record = false
true
end
def update_without_callbacks
update_attributes = attributes.except(self.class.primary_key)
update_attributes['created_at'] = Time.zone.now
update_attributes['updated_at'] = Time.zone.now
update_columns update_attributes
end
end
in any model:
include SaveWithoutCallbacks
then you can:
record.save_without_callbacks
or
Model::WithoutCallbacks.create(attributes)
# for rails 3
if !ActiveRecord::Base.private_method_defined? :update_without_callbacks
def update_without_callbacks
attributes_with_values = arel_attributes_values(false, false, attribute_names)
return false if attributes_with_values.empty?
self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
end
end
None of these points to without_callbacks plugin that just does what you need ...
class MyModel < ActiveRecord::Base
before_save :do_something_before_save
def after_save
raise RuntimeError, "after_save called"
end
def do_something_before_save
raise RuntimeError, "do_something_before_save called"
end
end
o = MyModel.new
MyModel.without_callbacks(:before_save, :after_save) do
o.save # no exceptions raised
end
http://github.com/cjbottaro/without_callbacks works with Rails 2.x
I wrote a plugin that implements update_without_callbacks in Rails 3:
http://github.com/dball/skip_activerecord_callbacks
The right solution, I think, is to rewrite your models to avoid callbacks in the first place, but if that's impractical in the near term, this plugin may help.
If you are using Rails 2. You could use SQL query for updating your column without running callbacks and validations.
YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}")
I think it should work in any rails versions.
For creating test data in Rails you use this hack:
record = Something.new(attrs)
ActiveRecord::Persistence.instance_method(:create_record).bind(record).call
https://coderwall.com/p/y3yp2q/edit
You can use sneaky-save gem: https://rubygems.org/gems/sneaky-save.
Note this cannot help in saving associations along without validations. It throws error 'created_at cannot be null' as it directly inserts the sql query unlike a model. To implement this, we need to update all auto generated columns of db.
For custom callbacks, use an attr_accessor and an unless in the callback.
Define your model as follows:
class Person << ActiveRecord::Base
attr_accessor :skip_after_save_callbacks
after_save :do_something, unless: :skip_after_save_callbacks
end
And then if you need to save the record without hitting the after_save callbacks you defined, set the skip_after_save_callbacks virtual attribute to true.
person.skip_after_save_callbacks #=> nil
person.save # By default, this *will* call `do_something` after saving.
person.skip_after_save_callbacks = true
person.save # This *will not* call `do_something` after saving.
person.skip_after_save_callbacks = nil # Always good to return this value back to its default so you don't accidentally skip callbacks.
Why would you want to be able to do this in development? Surely this will mean you are building your application with invalid data and as such it will behave strangely and not as you expect in production.
If you want to populate your dev db with data a better approach would be to build a rake task that used the faker gem to build valid data and import it into the db creating as many or few records as you desire, but if you are heel bent on it and have a good reason I guess that update_without_callbacks and create_without_callbacks will work fine, but when you are trying to bend rails to your will, ask yourself you have a good reason and if what you are doing is really a good idea.
One option is to have a separate model for such manipulations, using the same table:
class NoCallbacksModel < ActiveRecord::Base
set_table_name 'table_name_of_model_that_has_callbacks'
include CommonModelMethods # if there are
:
:
end
(Same approach might make things easier for bypassing validations)
Stephan
Another way would be to use validation hooks instead of callbacks. For example:
class Person < ActiveRecord::Base
validate_on_create :do_something
def do_something
"something clever goes here"
end
end
That way you can get the do_something by default, but you can easily override it with:
#person = Person.new
#person.save(false)
Something that should work with all versions of ActiveRecord without depending on options or activerecord methods that may or may not exist.
module PlainModel
def self.included(base)
plainclass = Class.new(ActiveRecord::Base) do
self.table_name = base.table_name
end
base.const_set(:Plain, plainclass)
end
end
# usage
class User < ActiveRecord::Base
include PlainModel
validates_presence_of :email
end
User.create(email: "") # fail due to validation
User::Plain.create(email: "") # success. no validation, no callbacks
user = User::Plain.find(1)
user.email = ""
user.save
TLDR: use a "different activerecord model" over the same table
I faced the same problem when I wanted to run a Rake Task but without running the callbacks for every record I was saving.
This worked for me (Rails 5), and it must work for almost every version of Rails:
class MyModel < ApplicationRecord
attr_accessor :skip_callbacks
before_create :callback1
before_update :callback2
before_destroy :callback3
private
def callback1
return true if #skip_callbacks
puts "Runs callback1"
# Your code
end
def callback2
return true if #skip_callbacks
puts "Runs callback2"
# Your code
end
# Same for callback3 and so on....
end
The way it works is that it just returns true in the first line of the method it skip_callbacks is true, so it doesn't run the rest of the code in the method.
To skip callbacks you just need to set skip_callbacks to true before saving, creating, destroying:
rec = MyModel.new() # Or Mymodel.find()
rec.skip_callbacks = true
rec.save
Not the cleanest way, but you could wrap the callback code in a condition that checks the Rails environment.
if Rails.env == 'production'
...

Resources