1

I made a User class containing a class method find_by_email:

class User
  attr_accessor :email
  def initialize
    @email="example@example.com"
  end
  def self.find_by_email(pattern)
    ObjectSpace.each_object(self) do |object|
      puts object.email+" "+(object.email.include? pattern).to_s        
    end
  end
end

In irb I try:

irb> user1=User.new
irb> user2=User.new
irb> user1.email="sergio@example.com"
irb> User.find_by_email "s"

which returns:

example@example.com false
sergio@example.com true

I would like find_by_email to return an array with the matching emails. So for this example it should only return ["sergio@example.com"]. How can I refactor the find_by_email class to achieve this?

builder-7000
  • 7,131
  • 3
  • 19
  • 43
  • 2
    Smashing around in ObjectSpace and looking for objects with particular properties is really not the best way to do this. You should have an array or hash structure storing them. Unless there's a reference to an object there's no reason for Ruby to keep it around, so theoretically it can discard both of the users you create here if you exit that scope. You must retain a reference somehow, and that's best done via a proper data structure. – tadman Jul 17 '19 at 23:18
  • @tadman, "smashing around in ObjectSpace" conjures [images](https://www.cagle.com/paul-zanetti/2016/12/the-trump-in-the-china-shop). – Cary Swoveland Jul 18 '19 at 00:03

3 Answers3

3

Your method does not return but only prints out what it finds.

You need to create an array, add to it, then return it.

  def self.find_by_email(pattern)
    # Create an empty array to store the emails.
    emails = []

    # Search!
    ObjectSpace.each_object(self) do |object|

      # If it matches, put the email in the array.
      if object.email.include? pattern
        emails << object.email
      end
    end

    # Return the array.
    emails
  end

And just a word of caution. You should probably stay away from ObjectSpace. This very much gets you under the hood of a ruby application, and into some territory where you may not be ready to go. I would recommend studying a lot more ruby foundational concepts before using it. And even then, it's probably a bad idea.

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
2

This is an example of the approach I believe @tadman is suggesting.

class User
  attr_accessor :email
  @users = []

  def initialize
    @email="example@example.com"
    self.class.instance_variable_get(:@users) << self
  end

  def self.select_by_email(pattern)
    instance_variable_get(:@users).select { |instance| 
      instance.email.include?(pattern) }
  end
end

user1=User.new
  #=> #<User:0x000055aaab80f6f0 @email="example@example.com"> 
user2=User.new
  #=> #<User:0x000055aaab8536c0 @email="example@example.com"> 
user1.email="sergio@example.com"
a = User.select_by_email "s"
  #=> [#<User:0x000055aaab80f6f0 @email="sergio@example.com">]

Note:

User.instance_variable_get(:@users)
  #=> [#<User:0x000055aaab80f6f0 @email="sergio@example.com">,
  #    #<User:0x000055aaab8536c0 @email="example@example.com">] 

We might then write:

a.map { |user| user.email }
  #=> ["sergio@example.com"]

I used the method Object#instance_variable_get, rather than creating a "getter" method for @users, because I do not want the content of that (class) instance variable to be accessible outside the class (though I guess I could have made that method private).

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

After reading the answer/comments I see how ObjectSpace could affect performance if there are many classes and instances to look for. So I decided to use the following class (using code from another question):

class User
  attr_accessor :email
  @@instance_collector = []
  def initialize
    @email="example@example.com"
    @@instance_collector << self
  end
  def self.find_by_email(pattern)
    @@instance_collector.collect do |instance|
      instance.email if instance.email.include? pattern
    end.compact
  end
end
builder-7000
  • 7,131
  • 3
  • 19
  • 43
  • 1
    I suggest `@@instance_collector.each_with_object([]) do |instance, arr| email = instance.email; arr << email if email.include? pattern; end`. Using `nil` to `select` is imo hackish and ugly. – Cary Swoveland Jul 18 '19 at 01:09