I'm trying to understand how this works and would be great if someone could help me. I've got a module which looks like the below:
module Activity::Track
extend ActiveSupport::Concern
module ClassMethods
def track_now(name, options={})
add_activity activity(verb: name)
end
end
protected
def activity(options={})
end
def add_activity(activity_item)
end
end
What I'm doing is calling the track_now method from my model, which is fine and works well but I can't call the add_activity and activity methods. I don't understand how I can call them from within the ClassMethods module. Can someone explain how I can do this?
I have seen this in another project but the protected methods were called by creating a define_method and then calling this via send :after_create :define_method. How does this work?
It seems you want to call an instance method from a class method. I don't recommend you doing that, but if you really want, you can try as following:
def track_now(name, options={})
self.new.instance_eval {add_activity activity(verb: name)}
end
In the end I created a new file track_now.rb which was a duplicate of track.rb but made all the methods a ClassMethod.
Does anyone know how I can use the protected methods from the instance and class methods from both files, this would be a great benefit?
Related
I don't know how to correctly phrase the title, I think the best way to explain this issue is just with code samples.
My goal
I want to define a meta method like this (in Rails 5):
class Post < ApplicationRecord
override_this_attribute_writer :some_attribute
end
The override_this_attribute_writer follows a common pattern, it overrides the original writer by doing some filtering on top of it. I find this way of overriding very convenient and clear.
First approach
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
alias_method :"#{attribute_name}_old=", :"#{attribute_name}="
define_method :"#{attribute_name}=" do |a_value|
# Do my stuff
send(:"#{attribute_name}_old=", a_value)
end
end
end
When doing this, I was getting an exception at the call of alias_method, because, apparently, the method I was trying to copy didn't exist (yet).
Second approach
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
define_method :"#{attribute_name}=" do |a_value|
# Do my stuff
send(:write_attribute, attribute_name, a_value)
end
end
end
I was expecting this not to work: if, when running the meta method, ActiveRecord hasn't created the attribute writer yet, this means that it will do it later and override the method that I just defined.
But surprisingly it worked! So I put my hands inside ActiveRecord (5.1.5) to find out more.
Dig into ActiveRecord 5.1.5
I wanted to ensure that what I did was safe and it wasn't just working by accident: I looked into the definition of method writer, and put binding.pry around the method.
This is the result of the experiment:
For attributes that I did not override,
This line is called
Then the method is defined inside this module eval call
Finally, the newly created writer method is correctly called when performing object.attribute=
For attributes that I DID override,
My own method is defined before anything else (when the ActiveRecord writers aren't there yet
Then ActiveRecord calls the same line that handles writer creation, as in the previous example
The method gets (apparently) correctly created by ActiveRecord, since it passes again by this point
But now, surprisingly, when calling object.attribute= my own method is still called in place of the ActiveRecord one
So, this is what I don't understand: if ActiveRecord seems to be overriding my method but it doesn't, what prevents it from doing it?
My questions
What in the end I need to know is whether the fix I have done is actually a good practice (and robust) or it's at risk and it might break if in the future we do upgrades.
If you think that my fix is dangerous, would you be able to suggest a different way to achieve the same goal?
Calling super is even more idiomatic:
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
define_method :"#{attribute_name}=" do |value|
# do some stuff
super value
end
end
end
end
I've defined a Sinatra helper in the usual way:
module Sinatra
module FooHelper
# code goes here
end
end
In my helper, among other things, I'd like to add a method to Numeric:
module Sinatra
module FooHelper
class ::Numeric
def my_new_method
end
end
end
end
However, in the interests of being unobtrusive, I only want to do add this method if my Sinatra helper is actually included in an application; if nobody runs helpers Sinatra::FooHelper, I don't want to affect anything (which seems like a reasonable thing to expect of a Sinatra extension).
Is there any hook that's fired when my helper is included, that would enable me to add my method only once that happens?
You can use the Module#included method to do this. I think you will need to modify your class definition slightly to use class_eval. I've tested the following and it works as expected:
module Sinatra
module FooHelper
def self.included(mod)
::Numeric.class_eval do
def my_new_method
return "whatever"
end
end
end
end
end
I'm a ruby noob and I'm a bit struggling finding the proper way to architecture my code.
In the code base, there is a Couch module that used to read:
module Couch
def self.client
#client ||= Couchbase.new "http://#{DATABASE_HOST}:8091/pools/default"
end
end
Each time we had to access the database we would do:
Couch.client.get(...)
Couch.client.set(...)
for instance :
def Model
def self.find(id)
Couch.client.get("foo:#{id}")
...
end
def save
Couch.client.set("foo:#{#id}", {...})
end
end
The repetition of Couch.client everywhere and static thingy was making me inconfortable, I felt it was not the ruby way.
So I changed the Couch module to
module Couch
extend Forwardable
def_instance_delegators :client, :get, :set, :delete, :append
def client
##client ||= Couchbase.new "http://#{hostname}:8091/pools/default"
end
end
and in model class that want to use it
def Model
extend Couch
def self.find(id)
get("foo:#{id}")
...
end
def save
Model.set("foo:#{#id}", {...})
end
end
I think it is better but I must admit I am not entirely satisfied with the way it looks...
Is it really OK to have a :get class method in my Model ? The name is a bit confusing. Should the module be included (with a ClassMethods submodule for instance) ? Can I avoid the class variable ##client (but I don't want a new connection to be created for each instance) ?
Bref, is there a better way ?
I think it is better but I must admit I am not entirely satisfied with the way it looks... Is it really OK to have a :get class method in my Model ? The name is a bit confusing. Should the module be included (with a ClassMethods submodule for instance) ? Can I avoid the class variable ##client (but I don't want a new connection to be created for each instance) ?
I don't think you should have a get class method in your Model; you're right that it makes no sense.
Your original method looks very good to me.
It's explicit, yet not too verbose. Anyone can understand what the code does, easily.
Take a look at http://github.com/couchbase/couchbase-ruby-model and couchbase-model rubygem itself. It does most of dirty job. Also if you have ideas/patches I'll be happy to review them. I'm using gerrit code review therefore the most recent patches are here http://review.couchbase.org/#/q/status:open+project:couchbase-ruby-model,n,z
I always have a brain cramp when it comes to this. I'm creating a module to mix in to model-like classes but it needs to keep exactly one copy of serializable attributes per class. So here is the code (that doesn't work).
module Checkin
module Model
def self.included(base)
base.extend(ClassMethods)
end
##serialiable_attrs = [] <== really not the right place
module ClassMethods
def serializable(*attrs)
attrs.each{|attr| ##serializable_attrs << attr} # Is this ## or just #?
end
end
def serialize!
##serializable_attrs.each{|a| do_something_with(a)} # <== Will this work?
end
end
end
class Person
include Checkin::Model
serializable :first_name, :original_name, :last_name, :checked_in, :people_attending
# etc., etc.
end
What I'm wrangling with are two things:
How to define my mix-in such that a class variable magically springs into existence; and
How to access that variable both in my ClassMethods module and in the (for lack of a better term) instance methods part of the module.
Note that I've settled on a mix-in technique rather than inheritance because I will have Validatable, Persistable, Serializable and so on. Also, I know there are all sorts of validation and persistence layers available that are well tested and heavily used. This is a different beast and I really should know who to do this in my sleep, right?
Any Ruby wizards who can help me understand how to do this or suggest a different direction to approach this problem from, I appreciate the help!
Try removing the class variable, and adding this to the module ClassMethod:
def self.extended(klass)
klass.instance_variable_set("#serializable_attrs", [])
end
And changing the double-# to single in serializable. And change serialize! to this:
self.class.instance_variable_get("#serializable_attrs").each{|a| do_something_with(a)}
Here's the situation:
I have a User model, and two modules for authentication: Oauth and Openid. Both of them override ActiveRecord#save, and have a fair share of implementation logic.
Given that I can tell when the user is trying to login via Oauth vs. Openid, but that both of them have overridden save, how do "finally" override save such that I can conditionally call one of the modules' implementations of it?
Here is the base structure of what I'm describing:
module UsesOauth
def self.included(base)
base.class_eval do
def save
puts "Saving with Oauth!"
end
def save_with_oauth
save
end
end
end
end
module UsesOpenid
def self.included(base)
base.class_eval do
def save
puts "Saving with OpenID!"
end
def save_with_openid
save
end
end
end
end
module Sequencer
def save
if using_oauth?
save_with_oauth
elsif using_openid?
save_with_openid
else
super
end
end
end
class User < ActiveRecord::Base
include UsesOauth
include UsesOpenid
include Sequencer
end
I was thinking about using alias_method like so, but that got too complicated, because I might have 1 or 2 more similar modules. I also tried using those save_with_oauth methods (shown above), which almost works. The only thing that's missing is that I also need to call ActiveRecord::Base#save (the super method), so something like this:
def save_with_oauth
# do this and that
super.save
# the rest
end
But I'm not allowed to do that in ruby.
Any ideas for a clever solution to this?
Is that what alias_method_chain would do? I've avoided that because people seemed to say it was a bad idea.
(Finding things as I go):
Alias Method Chain the Ruby Way
Yes alias method chain would help you in this situation.
But consider using delegate pattern. Original save method would trigger a callback on special delegate object (which can be as well nil) and it would do whatever needs to be done when saving user.
Also there is simliar pattern supported directly by actve record called Observer, try to read somethng about it maybe that's a good solution too.
I'm not saying this chaining methods is wrong, but there are cleaner ways to achieve what you want.