How to call a methods in multiple classes in ruby - ruby-on-rails

class One
class Two
class Three
def name
Faker::Name.name
end
end
def workflow
Three.new
end
end
def event
Two.new
end
def id
Faker::Number.number(4).to_i
end
end
I am new to ruby. Can someone help me, how to call all these methods in ruby?

Is this what you looking for?
one = One.new
two = One::Two.new
three = One::Two::Three.new
three.name
# => "Mr. Dillon Jacobson"
two.workflow
# => #<One::Two::Three:0x000055b2d9d70be0>
one.event
# => #<One::Two:0x000055b2df4160d0>
one.id
# => 6579413068

Pretty simple to do with instance_methods(false) that will give us all the defined instance methods of a class. We can then just push all the nested objs into an array and iterate over them. I'm not too sure on how to get all nested classes. You can however do that with Module.nesting
def call_all_methods(obj)
obj.class.instance_methods(false).each do |m|
obj.public_send(m)
end
end
[
One.new,
One::Two.new,
One::Two::Three.new
].each do |obj|
call_all_methods(obj)
end

Related

Rails N+1 query : monkeypatching ActiveRecord::Relation#as_json

Situation
I have a model User:
def User
has_many :cars
def cars_count
cars.count
end
def as_json options = {}
super options.merge(methods: [:cars_count])
end
end
Problem
When I need to render to json a collection of users, I end up being exposed to the N+1 query problem. It is my understanding that including cars doesn't solve the problem for me.
Attempted Fix
What I would like to do is add a method to User:
def User
...
def self.as_json options = {}
cars_counts = Car.group(:user_id).count
self.map do |user|
user.define_singleton_method(:cars_count) do
cars_counts[user.id]
end
user.as_json options
end
end
end
That way all cars counts would be queried in a single query.
Remaining Issue
ActiveRecord::Relation already has a as_json method and therefore doesn't pick the class defined one. How can I make ActiveRecord::Relation use the as_json method from the class when it is defined? Is there a better way to do this?
Edits
1. Caching
I can cache my cars_count method:
def cars_count
Rails.cache.fetch("#{cache_key}/cars_count") do
cars.count
end
end
This is nice once the cache is warm, but if a lot of users are updated at the same time, it can cause request timeouts because a lot of queries have to be updated in a single request.
2. Dedicated method
Instead of calling my method as_json, I can call it my_dedicated_as_json_method and each time I need to render a collection of users, instead of
render json: users
write
render json: users.my_dedicated_as_json_method
However, I don't like this way of doing. I may forget to call this method somewhere, someone else might forget to call it, and I'm losing clarity of the code. Monkey patching seems a better route for these reasons.
Have you considered using a counter_cache for cars_count? It's a good fit for what you're wanting to do.
This blog article also offers up some other alternatives, e.g. if you want to manually build a hash.
If you really wanted to continue down the monkey patching route, then ensure that you are patching ActiveRecord::Relation rather than User, and override the instance method rather than creating a class method. Note that this will then affect every ActiveRecord::Relation, but you can use #klass to add a condition that only runs your logic for User
# Just an illustrative example - don't actually monkey patch this way
# use `ActiveSupport::Concern` instead and include the extension
class ActiveRecord::Relation
def as_json(options = nil)
puts #klass
end
end
Option 1
In your user model:
def get_cars_count
self.cars.count
end
And in your controller:
User.all.as_json(method: :get_cars_count)
Option 2
You can create a method which will get all the users and their car count. And then you can call the as_json method on that.
It would roughly look like:
#In Users Model:
def self.users_with_cars
User.left_outer_joins(:cars).group(users: {:id, :name}).select('users.id, users.name, COUNT(cars.id) as cars_count')
# OR may be something like this
User.all(:joins => :cars, :select => "users.*, count(cars.id) as cars_count", :group => "users.id")
end
And in your controller you can call as_json:
User.users_with_cars.as_json
Here is my solution in case someone else is interested.
# config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
# config/initializers/core_extensions.rb
require 'core_extensions/active_record/relation/serialization'
ActiveRecord::Relation.include CoreExtensions::ActiveRecord::Relation::Serialization
# lib/core_extensions/active_record/relation/serialization.rb
require 'active_support/concern'
module CoreExtensions
module ActiveRecord
module Relation
module Serialization
extend ActiveSupport::Concern
included do
old_as_json = instance_method(:as_json)
define_method(:as_json) do |options = {}|
if #klass.respond_to? :collection_as_json
scoping do
#klass.collection_as_json options
end
else
old_as_json.bind(self).(options)
end
end
end
end
end
end
end
# app/models/user.rb
def User
...
def self.collection_as_json options = {}
cars_counts = Car.group(:user_id).count
self.map do |user|
user.define_singleton_method(:cars_count) do
cars_counts[user.id]
end
user.as_json options
end
end
end
Thanks #gwcodes for pointing me at ActiveSupport::Concern.

Single Table Inheritance or Type Table

I am facing a design decision I cannot solve. In the application a user will have the ability to create a campaign from a set of different campaign types available to them.
Originally, I implemented this by creating a Campaign and CampaignType model where a campaign has a campaign_type_id attribute to know which type of campaign it was.
I seeded the database with the possible CampaignType models. This allows me to fetch all CampaignType's and display them as options to users when creating a Campaign.
I was looking to refactor because in this solution I am stuck using switch or if/else blocks to check what type a campaign is before performing logic (no subclasses).
The alternative is to get rid of CampaignType table and use a simple type attribute on the Campaign model. This allows me to create Subclasses of Campaign and get rid of the switch and if/else blocks.
The problem with this approach is I still need to be able to list all available campaign types to my users. This means I need to iterate Campaign.subclasses to get the classes. This works except it also means I need to add a bunch of attributes to each subclass as methods for displaying in UI.
Original
CampaignType.create! :fa_icon => "fa-line-chart", :avatar=> "spend.png", :name => "Spend Based", :short_description => "Spend X Get Y"
In STI
class SpendBasedCampaign < Campaign
def name
"Spend Based"
end
def fa_icon
"fa-line-chart"
end
def avatar
"spend.png"
end
end
Neither way feels right to me. What is the best approach to this problem?
A not very performant solution using phantom methods. This technique only works with Ruby >= 2.0, because since 2.0, unbound methods from modules can be bound to any object, while in earlier versions, any unbound method can only be bound to the objects kind_of? the class defining that method.
# app/models/campaign.rb
class Campaign < ActiveRecord::Base
enum :campaign_type => [:spend_based, ...]
def method_missing(name, *args, &block)
campaign_type_module.instance_method(name).bind(self).call
rescue NameError
super
end
def respond_to_missing?(name, include_private=false)
super || campaign_type_module.instance_methods(include_private).include?(name)
end
private
def campaign_type_module
Campaigns.const_get(campaign_type.camelize)
end
end
# app/models/campaigns/spend_based.rb
module Campaigns
module SpendBased
def name
"Spend Based"
end
def fa_icon
"fa-line-chart"
end
def avatar
"spend.png"
end
end
end
Update
Use class macros to improve performance, and keep your models as clean as possible by hiding nasty things to concerns and builder.
This is your model class:
# app/models/campaign.rb
class Campaign < ActiveRecord::Base
include CampaignAttributes
enum :campaign_type => [:spend_based, ...]
campaign_attr :name, :fa_icon, :avatar, ...
end
And this is your campaign type definition:
# app/models/campaigns/spend_based.rb
Campaigns.build 'SpendBased' do
name 'Spend Based'
fa_icon 'fa-line-chart'
avatar 'spend.png'
end
A concern providing campaign_attr to your model class:
# app/models/concerns/campaign_attributes.rb
module CampaignAttributes
extend ActiveSupport::Concern
module ClassMethods
private
def campaign_attr(*names)
names.each do |name|
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{name}
Campaigns.const_get(campaign_type.camelize).instance_method(:#{name}).bind(self).call
end
EOS
end
end
end
end
And finally, the module builder:
# app/models/campaigns/builder.rb
module Campaigns
class Builder < BasicObject
def initialize
#mod = ::Module.new
end
def method_missing(name, *args)
value = args.shift
#mod.send(:define_method, name) { value }
end
def build(&block)
instance_eval &block
#mod
end
end
def self.build(module_name, &block)
const_set module_name, Builder.new.build(&block)
end
end

How to write a method trailing with a keyword

I have a situation where i need to call something like this :
class Office
attr_accessor :workers, :id
def initialize
#workers = []
end
def workers<<(worker)
type = worker.type
resp = Organiation::Worker.post("/office/#{#id}/workers.json", :worker => {:type => type})
end
end
this is where i need to call
office = Office.new()
new_worker = Worker.new()
office.workers << new_worker
how should i modify the above workers method in order to implement above code.
New answer for this (based on updated question):
class WorkersClient
attr_accessor :office_id
def <<(worker)
type = worker.type
resp = Organiation::Worker.post("/office/#{#office_id}/workers.json", :worker => {:type => type})
end
end
class Office
attr_accessor :workers, :id
def initialize
#workers = WorkersClient.new
#workers.office_id = #id
end
end
I'm assuming that the Worker class is defined somewhere, something like:
def Worker
attr_accessor :type
...
end
The WorkersClient class is just a proxy to handle the collection (like ActiveRecord 3 does with associations). You can develop it further to store a local cache of workers, and so on.
I would recommend looking at how Rails' ActiveResource is implemented, as it does something VERY similar.
try this office.build_worker
If those objects are actually ActiveRecord objects (which it sort of sounds like), you're probably looking at
office.workers << new_worker
Note the plural form.
If those objects are your own creations, you probably want Office#workers to return an Array'ish object, so something like
class Office
def workers
#workers ||= []
end
end
Add sanity checks and whatnot as you see fit.
There's not much to add to what's already been said, but one thing to think about is hiding the implementation of workers. Sure, it starts out with an array, but that may change. By implementing your own << method you can hide implementation details from the user.
class Office
attr_accessor :workers
def initialize
#workers = []
end
def <<(other)
self.workers << other
end
end
I tend to use getter/setters inside my classes as that's something I learned from Smalltalk books, but of course you could just do #workers << other.

Create new records on before_save

While creating new records. I need to create more records for the same model.
Example ::
class XYZ < ActiveRecord
def before_save
# At this point object is already initialized ..
# And it's containing values.
# At this point i want to create 10 more records for the same class.
# something like this
XYZ.new(:att1 => value1,:att2 => value2,:att3 => self.att1)
end
end
How may i handle this type of scenario ?
On which call back i have to create more records for the same model ?
First, this sounds like bad engineering, try to rethink your model in a way that makes what you need.
maybe if you need to create 10 models of something, do not use the activerecord hooks, otherwise you might incur in infine loops.
I would recommend
class XYZ < ActiveRecord
def self.create10(original_xyz)
10.times do
clone = original_xyz.clone
clone.save
end
end
end
and where in your controller or wherever you have the need to create 10 more, call:
new_xyz = XYZ.new(:att1 => value1,:att2 => value2,:att3 => self.att1)
new_xyz.save
XYZ.create10(new_xyz)
but if you really need to create 10 more on a hook (like before save), do:
class XYZ < ActiveRecord
before_save create10
attr_acessor :cloned
def create10
return if cloned # this will prevent infinit loooooooooooooooop
10.times do
clone = self.clone
clone.cloned = true
clone.save
end
end
end
I did not run this, so, try it first.
class XYZ < ActiveRecord
def after_initialize
# At this point object is already initialized ..
# And it's containing values.
# At this point i want to create 10 moew records for the same class.
# something like this
#XYZ.new(:att1 => value1,:att2 => value2,:att3 => self.att1)
x = 10 #an integer
x.times do |task|
Model.create(:key => :value)
end
end
end

Active Record like functionality on array instance variable

I would like to write a module that provides active record like functionality on an array instance variable.
Examples of its use would be
x = Container.new
x.include(ContainerModule)
x.elements << Element.new
x.elements.find id
module ContainerModule
def initialize(*args)
#elements = []
class << #elements
def <<(element)
#do something with the Container...
super(element)
end
def find(id)
#find an element using the Container's id
self
#=> #<Array..> but I need #<Container..>
end
end
super(*args)
end
end
The problem is that I need the Container object within these methods. Any reference to self will return the Array, not the Container object.
Is there any way to do this?
Thanks!
Would something like this work for you?
class Container
attr_accessor :elements
def initialize
#elements = ContainerElements.new
end
end
class ContainerElements < Array
def find_by_id(id)
self.find {|g| g.id == id }
end
end
So i create a container-class, and a ContainerElements that inherits from Array, with an added (specific) find_by_id method.
If you really want to call it find you need to alias it.
Example code would be:
class ElemWithId
attr_accessor :id
def initialize(value)
#id = value
end
end
cc = Container.new
cc.elements << ElemWithId.new(1)
cc.elements << ElemWithId.new(5)
puts "elements = #{cc.elements} "
puts "Finding: #{cc.elements.find_by_id(5)} "
Hope this helps ...
Your best approach may be to work with the Hash class, which has operations like finding by id already. Particularly, the fetch method may help you out.

Resources