0

I want to have a method called delete_commas that deletes commas within a string. Below is what I have:

def delete_commas
  gsub(',','')
end

string = "this string, has, alot, of, commas,,,"

I expect:

string.delete_commas # => "this string has alot of commas"

This does not work, it raises an error:

NoMethodError: private method 'delete_commas' called for "this string, has, alot, of, commas,,,":String

I could do this:

def delete_commas(string)
  string.gsub(',','')
end
delete_commas(string)

but that is not how I want to do it. Wondering if I could get some refactoring help.

I actually created a gem for this https://github.com/txssseal/delete_commas

Colton Seal
  • 379
  • 2
  • 14
  • 1
    What's your question? – Pavan Jul 02 '15 at 16:45
  • how can I create the method so when I call string.delete_commas it will delete the commas – Colton Seal Jul 02 '15 at 16:46
  • 1
    updated the question – Colton Seal Jul 02 '15 at 16:47
  • 1
    the private method issue is due to the context in which you placed the method. When adding a method in irb it is privatized in the context of `main:Object` this means that while you added the method near the highest level (`Object`) you cannot call it directly like you tried to because it is private in this context. what you need to do is, as @shivam suggested, add the method to the `String` class. – engineersmnky Jul 02 '15 at 16:55
  • 1
    Colton, it appears that you have a fundamental misunderstanding of how methods are invoked in Ruby. When you execute `string.delete_commas`, the method `delete_commas` is sent to the object `string`, which is called the "receiver". Ruby looks for an instance method `delete_commas` in the receiver's class (`string.class #=> String`). That's why you must define `delete_commas` in the class `String`, as shivram advises. It's actually a little more complicated than this. See, for example, @7stud's answer [here](http://stackoverflow.com/questions/23848667/ruby-method-lookup-path-for-an-object). – Cary Swoveland Jul 02 '15 at 17:11
  • @CarySwoveland just as a note technically he is declaring an instance method accessible from `String` because he is declaring it in the context of `Object`. The problem is `irb` privatizes these methods so they can only be acessed from `main:Object`. This is evident by pasting his method into `irb` and then try `"st,r,in,g".delete_commas #=>NoMethodError` and `"st,r,in,g".send(:delete_commas) #=> "string"` – engineersmnky Jul 02 '15 at 17:17
  • @engineersmnky, thanks. I was thinking the OP's code was inside an unspecifed class, but I see it's in `main`. Still, I suspect there may some confusion about how Ruby dispatches methods, as no mention was made of the class `Object`. – Cary Swoveland Jul 02 '15 at 17:25
  • Correct, I had fundamental ideas of adding the method in the String class, but I was wondering if it was possible to do it without adding a method to the string class – Colton Seal Jul 02 '15 at 18:40

3 Answers3

4

Define a method to String class as in string.delete_commas, delete_commas is supposed to be a String method

class String
   def delete_commas
     self.gsub(',','')
   end
end

string = "this string, has, alot, of, commas,,,"

string.delete_commas
# => "this string has alot of commas"
shivam
  • 16,048
  • 3
  • 56
  • 71
  • yaa. I could do that, but I really do not want to create/edit a class to do all of that. Wanted to do it within my current class. – Colton Seal Jul 02 '15 at 16:48
  • You are not creating any new class. Your `string` is an object of String class. You are just defining a method for that class. – shivam Jul 02 '15 at 16:49
  • ya, I guess I could do that, not being a big deal – Colton Seal Jul 02 '15 at 16:50
  • 2
    @ColtonSeal this is exactly what you requested since you said that you didn't want to pass a receiver like `delete_commas(string)` but rather wanted to call the method directly as `string.delete_commas`. Also `delete` might suggest mutation not sure if that is what you wanted or not but this method will not mutate the string but rather return a copy without commas. – engineersmnky Jul 02 '15 at 16:52
  • true, I could rename it to replace_ commas. – Colton Seal Jul 02 '15 at 16:53
  • @engineersmnky within the `String` class, `!` is usually used to indicate mutation, e.g. `String#delete` vs. `String#delete!` – Stefan Jul 02 '15 at 16:58
  • That is much nicer looking – Colton Seal Jul 02 '15 at 17:01
  • @stefan I understand *dangerous* methods but in many other classes `Hash`, `Array`, etc. `delete` no bang(`!`) still mutates the receiver that's why I said *"might suggest"*. Also like you stated `!` *"usually"* indicates because things like `replace` and `clear` have no bang but does mutate. – engineersmnky Jul 02 '15 at 17:01
3

Do this:

string.delete ","

That's it. No need to reinvent a Ruby built-in.

Mark Thomas
  • 37,131
  • 11
  • 74
  • 101
2

As noted by @Mark Thomas, the functionality you are specifically looking for is already a part of the language, by using:

"this string, has, alot, of, commas,,,".delete ","

but if you are looking to add new functionality anyway, there are a few ways to do it.

Looking at your desired code:

string = "this string, has, alot, of, commas,,,"
string.delete_commas # => "this string has alot of commas"

You have to look at what object is receiving the message you're sending. In this case the "this string, has, alot, of, commas,,," object, which is an instance of String. In order for that object to respond to that message, the String class needs to implement a method of that name, as @shivam suggests.

It is important to note that this is what is affectionately referred to as a monkey patch. This comes with some drawbacks, some of which are listed in the article I linked.

A safer way of attaining this goal as of Ruby 2.0 is Refinements. By declaring a module that defines the refinement to the string class, you can choose exactly the scope at which you want to refine strings:

module ExtraStringUtils
  refine String do
    def delete_commas
      gsub(',','')
    end
  end
end

class MyApplication
  using ExtraStringUtils

  "this string, has, alot, of, commas,,,".delete_commas
    #=> this string has alot of commas
end

"this string, has, alot, of, commas,,,".delete_commas 
  #=> NoMethodError: undefined method `delete_commas' for "this string, has, alot, of, commas,,,":String

In other words, you have access to your special changes to String wherever you are using ExtraStringUtils, but nowhere else.

One last way to have a similar outcome is to have your own class that implements that method and operates on a string internally:

class MySuperSpecialString
  def initialize string
    @string = string
  end

  def delete_commas
    @string.gsub!(',','') #gsub! because we want it to permenantly change the stored string
  end
end

At which point the usage would be:

string = MySuperSpecialString.new("this string, has, alot, of, commas,,,")
string.delete_commas # => "this string has alot of commas"

This approach comes with its own special set of drawbacks, the biggest of which being that it does not respond to all the methods a string would. You can use delegation to pass unknown methods to the string, but there are still some seams where it won't behave exactly like a string and it won't behave exactly as a separate class, so be aware of the return values.

Alexis Andersen
  • 785
  • 6
  • 12
  • 1
    depending on context this would be a great use for refinements as well. (sort of safe monkey patching) – engineersmnky Jul 02 '15 at 17:23
  • Thatnks @engineersmnky that's a good call out. I'll add something about it. – Alexis Andersen Jul 02 '15 at 18:17
  • While I am glad that you liked my suggestion my thought was if he only needed this functionality inside another class then refinements would work well e.g. `class MyApplication; using ExtraStringUtils; end` now inside of `MyApplication` every `String` would have access to `delete_commas` but not outside the class. Right now your refinement is no different than calling `gsub` inside `speak_without_extra_commas`. Mostly posted refinements comment because it is a lesser known functionality for scoping monkey patches. – engineersmnky Jul 02 '15 at 18:45
  • @engineersmnky I updated the refinements example per your suggestions. I agree this is more clear as to the value it's providing. Thanks. – Alexis Andersen Jul 02 '15 at 19:55