Nik So
Nik So

Reputation: 16811

ruby how to have two optional arguments but need at least one present?

I have this method, #upload, that basically takes in, amongst other arguments, a file option or some text. If a file (File.open("test.txt",'r+')) is passed,upload(:file => ...) , then this method reads from the file for the text, or if someone decides to pass that very same text just not in a txt file, he can do upload(:content => ...)

But I need either a file with the text or the text itself passed, how would you approach this?

Here's what I have so far.

 def upload(args)
   if args[:content].present?
     self.content = args[:content]
   elsif args[:file].present?
     self.content = args[:file].read
   end
 end

thanks!

Upvotes: 2

Views: 557

Answers (3)

sawa
sawa

Reputation: 168101

Solution 1

If the class of the argument is different (String vs. File), then you can use that in case construction. You do not need further information to distinguish that.

def upload(arg)
  self.content =
  case arg
  when String; arg
  when File; arg.read
  end
end

Solution 2

Added after inspired by a comment by Nemo157

Using polymorphism of object oriented programming, you can do this:

def upload(arg); self.content = arg.upload end
class String
  def upload; self end
end
class File
  def upload; read end
end

Some notes on polymorphism

Often, we refer to similar but different actions under the same word. For example, consider the word add in the context of ordinary life: we use it in different meanings: add water to the jar, add 3 to 1, add a comment, add shade of blue to the green paint, and so on. They have different meanings but we have the intuition that they are somehow related. One way to distinguish these meanings will be to use different words, like liquid-add, number-add, context-add, or may be you can number them like add1, add2, add3, but this is a mess. However, notice that their meaning depend largely on the type of the object it is predicated of: depending on whether it's liquid, number, discourse, etc., the appropriate sense of "add" while be determined. The idea of polymorphism uses this fact, and applies it to programming. In the case here, "upload" will have different meaning depending on whether its about a string or a file. But as long as they are defined within their respective class, you don't have to care about the difference when you use them. Hence, you are freed from case statement, and makes the code simpler.

Upvotes: 2

Nemo157
Nemo157

Reputation: 3589

Just make it a test in the method, something like

def upload(args)
  if args[:content].present?
    self.content = args[:content]
  elsif args[:file].present?
    self.content = args[:file].read
  else
    raise ArgumentError, 'one of :content or :file must be given'
  end
end

EDIT: alternative that uses just a single argument and duck-typing

def upload(arg)
  if arg.respond_to? :to_str
    self.content = arg.to_str
  elsif arg.respond_to? :read
    self.content = arg.read
  else
    raise ArgumentError, 'a String-like or IO-like object must be given'
  end
end

Similar to the other two but with what I feel are a couple of improvements

  • Duck-typing instead of basing behaviour on the class, this means other classes could be used as long as they implement the right methods. Mainly this allows other forms of IO other than just normal files. Could be done with sub-typing, but that's not as much of a requirement in Ruby.

  • Using to_str instead of to_s, every class has a to_s method and I'm assuming that you're not wanting to upload content like #<MyClass:0x523e>. to_str on the other hand is only implemented by classes that specifically should be convertible to a string.

Upvotes: 0

Jonathan
Jonathan

Reputation: 3253

Do you specifically want to pass the argument as a hash? If not I would go for something like:

def upload(args)
  if args.respond_to? :read
    self.content = args.read
  else
    self.content = args.to_s # This allows non-string arguments.
  end
end

Upvotes: 2

Related Questions