I make an API call:
def set_youtube(user)
Youtube.get_subscribers(user)
Youtube.get_views(user)
end
Here's my service object:
class Youtube
class << self
def get_hash(user)
## code to return a youtube JSON hash containing subscribers and views
end
def get_subscribers(user)
youtube_hash = Youtube.get_hash(user)
## code to return a subscriber count
end
def get_views(user)
youtube_hash = Youtube.get_hash(user)
## code to return a view count
end
end
end
However, I find it more elegant to call the method directly on the user. I don't want to make two calls to the API to get subscribers and then get views. But I also don't want to do:
youtube_hash = Youtube.get_hash(user)
Youtube.get_subscribers(youtube_hash)
Youtube.get_views(youtube_hash)
I want to temporarily cache the variable in the instance of this object so that I can use it for both class methods. What's the correct way to handle this?
You could use class variables (prefixed with ## symbols) and cache the hash, however you will then have to maintain that and it could get messy, instead I suggest using a more OO approach?
You could make it an instance of "Youtube", and cache the hash
class Youtube
def initialize(user)
#user = user
end
def hash
#hash ||= ... #the logic used to get the user hash in your get_hash using the #user instance variable
end
def subscribers
#subscribers ||= ... #the logic used to get the user subscribers in your get_subscribers however using the hash getter method which in turn uses the #hash instance variable
end
def views
#views ||= ... #the logic used to get the user views in your get_views however using the hash getter method which in turn uses the #hash instance variable
end
end
Then you can do the following and it will use the cached hash:
yt = Youtube.new(user: user)
yt.views
yt.subscribers
Maybe "YoutubeUser" is a better name? Just a suggestion. This all also could be moved to the User model and just use the Youtube service object. For example:
class User
before_create :set_youtube
def set_youtube
youtube = Youtube.new(self)
self.youtube_subscribers = youtube.subscribers
self.youtube_views = youtube.views
end
end
I assume set_youtube is an instance method, therefore no need to pass the user, however the class method would be similar as well.
Related
Using SimpleDelegator, I created a few decorators to add extra functionalities to my objects. I need to decorate an object twice, as below:
Tracked.new(Audited.new(User.new))).save
Here is the basic structure of the decorator(s):
class Tracked #or Audited
delegate :id, to: :__getobj__
def initialize(extened_object)
super(extened_object)
#extened_object = extened_object
end
def itself
__getobj__
end
def save
super
# the extended behavior
end
end
I want to access the class User from the object. In Audited, if I call #extended_object.class with Audited.new(User.new)).save, I get User. In Tracked, if I call #extended_object.class with Tracked(Audited.new(User.new))).save, I get Audited instead.
How can I get the Class of the extended_object regardless of the number of times I decorate it?
I don't think you can do this with SimpleDelegator.
You need to implement this method yourself.
For example:
class MyDelegator < SimpleDelegator
def original_object
obj = __getobj__
obj.is_a?(MyDecorator) ? obj.original_object : obj
end
end
And all of your decorators should be inherited from MyDelegator
I have a class Klass, and its constructor accepts an argument. We should be able to call methods on this object that are not defined in Klass.
We can chain multiple methods, but in the end, we have to use Klass#result to get the result like:
Klass.new(5).pred.pred.result
and the output here should be 3. I tried using method_missing in Klass and using send on the object's class, but that would have worked without the result method that I have to use. Can someone explain how this can be done with delegation?
You could do something like this:
class Klass
def initialize(number)
#number = number
end
def result
#number
end
def method_missing(method_name, *arguments, &block)
if #number.respond_to?(method_name)
#number = #number.method(method_name).call(*arguments, &block)
return self
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
# be sure to implement this...
end
end
puts Klass.new(5).pred.pred.result # => 3
But it's problematic. In this particular example, since #pred returns a new object (it doesn't modify the object it was called on), we have to reassign the instance variable to the result. It works for pred and other methods that return new Integers, but some methods on Integer don't return an Integer (e.g. Integer#even). In this case you'd get this sort of behavior:
puts Klass.new(4).even?.result # => true
Depending on your particular situation, that might be what you're after. Or, it might be that in your situation all methods the object being delegated to mutate that object, rather than return new instances of the object, in which case the reassignment isn't needed.
I don't think you can use Ruby's existing Delegator and SimpleDelegator constructs, because the only way you can chain the final #result call onto the end is if every delegated call returns the instance of Klass. Using those existing constructs would cause delegated calls to return their normal return values, and the chaining would then be on whatever objects those return values return. For example, using the above code, you'd see this behavior:
puts Klass.new(5).pred.pred.class # => "Klass"
Using SimpleDelegator, you'd see this behavior
require 'delegate'
class Klass2 < SimpleDelegator
# Klass2 methods...
end
puts Klass2.new(5).pred.pred.class # => "Fixnum"
Hope that helps.
I'm trying to implement a gem that is redis wrapper for other library i.e to store the ruby object in the redis.
all work well but what I want is when I do
[Class].all
It give object like this
[#<Peagon:0x007fba589de3a0 #name="a", #omg=false ,#payload="one",#handler="--- one\n...\n"> ,#<Peagon:0x007fba589de1a0 #name="b", #omg=true,#payload="two",#handler="--- two\n...\n">]
but instead I want it to be look like how active record present the object
[#<Peagon name: "a",omg: false ,handler: "--- one\n...\n"> ,#<Peagon name="b", omg: true,handler: "--- two\n...\n">]
The reason for this that I not interested in showing the user the #payload instance variable because that is something set by the other library
so basically like this happen
[My gem]
class Peagon
include SomeModule
attr_accessor :name,:omg,:handler
def initialize(options)
#name = options[:name]
#omg = options
self.payload_object = options[:payload_object]
end
end
Now the [Other Library] has this module in it
module SomeModule
def payload=(object)
#payload ||= object
self.handler = #payload.to_yaml
end
def payload
#payload ||= YAML.load(self.handler)
end
end
NOTE :
Overwriting the payload method from other library is not in my mind
Now is it possible to get what I meant above
Looks like you just want to adjust what irb, the Rails console, and friends will display for objects of your class. If so, they just call inspect:
inspect → string
Returns a string containing a human-readable representation of obj. By default, show the class name and the list of the instance variables and their values (by calling inspect on each of them). User defined classes should override this method to make better representation of obj.
So all you need to do is provide your own inspect implementation, something like:
def inspect
"#<#{class} name: #{#name.inspect} ...>"
end
First, for the short version:
Isn't a method definition just a block? Why can't I do something like:
obj.instance_exec(&other_obj.method(:my_method))
with the goal of running some module method in the context of an instance of a separate class? The method is called, but it doesn't seem to be executed in the context of 'obj', despite the 'instance_exec' call.
The only way I can figure out how to accomplish this is to wrap all of the code of 'my_method' in a proc, then call in the following manner instead:
obj.instance_eval(&other_obj.my_method)
but I'd like to avoid encapsulating all of my module methods in procs.
Now, for the long version:
I'm attempting to create a modularized external provider system, where for any given class/method (generally controller methods,) I can call a corresponding method for a given provider (e.g. facebook).
Since there could be multiple providers, the provider methods need to be namespaced, but instead of simply including a bunch of methods like, for example, 'facebook_invitation_create', I'd like my InvitationsController instance to have a facebook member containing a create method - e.g.
class InvitationsController < ApplicationController
def create
...
# e.g. self.facebook.create
self.send(params[:provider]).create
...
end
end
Furthermore, I'd like the provider methods to not only function as if they were part of the controller itself - meaning they should have access to things like controller instance variables, params, session, etc. - but also to be (mostly) written as if they were part of the controller itself - meaning without any complex additional code as a result of being modularized.
I've created a simplified example below, in which MyClass has a greet method, which if called with a valid provider name (:facebook in this case), will call that providers greet method instead. In turn, the provider greet method accesses the message method of the including class, as if it were part of the class itself.
module Providers
def facebook
#facebook ||= FacebookProvider
end
module FacebookProvider
class << self
def greet
proc {
"#{message} from facebook!"
}
end
end
end
end
class MyClass
include Providers
attr_accessor :message
def initialize(message="hello")
self.message = message
end
def greet(provider=nil)
(provider.nil? or !self.respond_to?(provider)) ? message : instance_exec(&self.send(provider).greet)
end
end
This actually accomplishes almost everything I've previously stated, but I'm hung up on the fact that my provider functions need to be encapsulated in procs. I thought maybe I could simply call instance_exec on the method instead (after removing the proc encapsulation):
instance_exec(&self.send(provider).method(:greet))
...but then it seems like the instance_exec is ignored, as I get the error:
NameError: undefined local variable or method `message' for Providers::FacebookProvider:Module
Is there any way to call instance_exec on a defined method?
(I'm open to suggestions on how to better implement this as well...)
I think this is simpler than you might expect (and I realize that my answer is 2 years after you asked)
You can use instance methods from modules and bind them to any object.
module Providers
def facebook
#facebook ||= FacebookProvider
end
module FacebookProvider
def greet
"#{message} from facebook!"
end
end
end
class MyClass
include Providers
attr_accessor :message
def initialize(message="hello")
self.message = message
end
def greet(provider=nil)
if provider
provider.instance_method(:greet).bind(self).call
else
message
end
end
end
If your provider is a module, you can user instance_method to create an UnboundMethod and bind it to the current self.
This is delegation.
It's the basis for the casting gem which would work like this:
delegate(:greet, provider)
Or, if you opt-in to using method_missing from casting, your code could just look like this:
greet
But you'd need to set your delegate first:
class MyClass
include Providers
include Casting::Client
delegate_missing_methods
attr_accessor :message
def initialize(message="hello", provider=facebook)
cast_as(provider)
self.message = message
end
end
MyClass.new.greet # => "hello from facebook!"
I wrote about what delegation is and is not on my blog which is relevant to understanding DCI and what I wrote about in Clean Ruby
Maybe I'm not following along, but it seems like you are making this harder than it needs to be.
Why not implement a "dispatch" pattern in your class, where you have a hash of provider names and provider methods {:facebook=>"facebook_greet"} and then just "send" the incoming call to the correct handler via "Object#send" (http://ruby-doc.org/core-1.9.3/Object.html#method-i-send)? Send is very fast for dispatching methods, so unlike eval, you should get great performance.
Here's some code to demonstrate the way I'd solve it (assuming I am following along with what you're trying to accomplish):
module TwitterProvider
def providerInit(providers)
#providers[:twitter]="twitter_greet"
super(providers) if defined?(super)
end
def twitter_greet
"Hello Twitter User"
end
end
module FacebookProvider
def providerInit(providers)
providers[:facebook]="facebook_greet"
super(providers) if defined?(super)
end
def facebook_greet
"Hello Facebook User"
end
end
class MyClass
include FacebookProvider
include TwitterProvider
attr_accessor :message
def providerInit(providers)
super(providers) if defined?(super)
end
def initialize(message="hello")
#providers = {}
self.message = message
providerInit(#providers)
end
def greet(provider=nil)
if provider.nil? or !self.respond_to?(#providers[provider])
self.message
else
self.send(#providers[provider])
end
end
end
my_class = MyClass.new
puts my_class.greet
puts my_class.greet(:twitter)
puts my_class.greet(:facebook)
# Output:
# hello
# Hello Twitter User
# Hello Facebook User
I need some class or singleton object globally-accessible in controllers and easy to use. Now it is implemented in the libs/ folder like this:
class User
class << self
#user = nil
attr_reader :uid, :name
def init session
if session[:user_info].nil?
#user = nil
end
#user = session_data[:user]
end
def signed_in?
#user.nil? ? false : true
end
def guest?
not signed_in?
end
end
end
This code obviously is not good, as User initialized only once on application start, and in case of improper use User wouldn't be updated. I want to save ability to use the class or object without much addition steps, but have a new global instance for every new connection. How it should be done?
It looks like you're trying to create a standard "current user" method. I think you're complicating things a bit. All you need to do is load a user object based on session information and cache it in an instance variable. Something like this:
class ApplicationController < ActionController::Base
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
The first time you call it, it will look up the current user based on the ID stored in the session. Subsequent calls will return the user object you already loaded. If the user is not signed in, current_user will simply be nil.
You can add include Singleton to your User class definition and then use User.instance go get the user instance.
Place the code in Application Controller as this is the base class of all the classes. Doing so it will be globally-accessible in all the controllers as well.