Is it acceptable having parameter in class constructor? - ruby

a rubygem I'm writing and that is useful for counting word occurrences in a text, I choose to put 3 parameters in class constructor.
The code is working, but I want to refactor it for niceness.
In your experience, it's easier to read/mantain/use as API a class with a constructor with no params and a lot of setters/getters method or a code like this one, with all the params in the constructor?
TIA
Paolo
def initialize(filename, words, hide_list)
if ! filename.nil?
#filename = filename
#occurrences = read
else
#filename = STDIN
#occurrences = feed
end
#hide_list = hide_list
#sorted = Array(occurrences).sort { |one, two| -(one[1] <=> two[1]) }
#words = words
end

You could do it the rails way, where options are given in a hash:
def initialize(filename = nil, options = {})
#hide_list = options[:hide_list]
#words = options[:words]
if filename
#filename = filename
#occurrences = read
else
#filename = STDIN
#occurrences = feed
end
#sorted = Array(occurrences).sort { |one, two| -(one[1] <=> two[1]) }
end
Then you can call it like this:
WC.new "file.txt", :hide_list => %w(a the to), :words => %w(some words here)
or this:
wc = WC.new
wc.hide_list = %w(a the is)
wc.words = %w(some words here)

In my experience I can tell that the very reason for allowing constructor parameters in most languages, apart from the fact of increasing the easiness in class instantiation, is to make it easy to use the API.
Favoring constructor, over getter/setter instantiation, also helps immutability, that is, creating an object thorough its constructor, and not letting anyone modify its properties later on.

I dont know how it is in Ruby, but in other languages you usually put those arguments in the constructor signature that are needed to initialize the object into a valid state. All other state can be set through setters.

Related

Dynamically creating objects in Ruby

I have a class whose initialize method defines a few instance variables and does some calculations. I need to create about 60 objects of that class. Each object has an ID number at the end. E.g.:
object1 = Dynamic.new(x, y)
object2 = Dynamic.new(x, y)
object3 = Dynamic.new(x, y)
...
I could just define them all by hand, but that would be quite inefficient. Is there any way to dynamically create each object?
You can always make a loop and push all the objects into an array. An array position might also be needed for knowing which object is each. This isn't quite what you wanted (atleast I don't think so), but it should suffice.
class Dynamic
##instances_of_class = 0
def initialize(x,y)
#...
#array_position = ##instances_of_class
##instances_of_class += 1
end
end
ary = []
50.times do
ary << Dynamic.new(x,y)
end
Edit: This solution, as said in the comments, can cause bugs if you change the array, so here's an alternate solution.
require 'File.rb'
i = 1
varFile = File.open("File.rb","a+")
50.times do
varFile.puts "variable#{i} = Object.new"
i += 1
end
Inside of File.rb will be 50 uniquely named variables that you can use.
I would be curious to know why you need this. It's an unusual requirement, and often that means that you can avoid the problem instead of solving it. I think TheLuigi's solution would work, but if you use a class variable then these Id's will be shared across multiple classes. You can instead use an instance variable, with something like the following:
class A
def self.next_id
#id ||= 0 ; #id += 1
end
def initialize
#id = A.next_id
end
end
A.new
# => #<A:0x007fd6d414c640 #id=1>
A.new
# => #<A:0x007fd6d41454a8 #id=2>
If you just want sixty objects accessible from a variable, you should have them in an array referred to by a single variable.
objects = Array.new(60){Dynamic.new(x, y)}
Your object1, object2, ... will correspond to objects[0], objects[1], ... respectively.

Ruby - Invoking a class from a CONSTANT that contains the class name

I have a class that calls different suppliers to find if an item is available. How do I execute the class that each constant returns?
class ItemProvider
ADAPTER_ONE = Providers::ItemFromSupplierOne
ADAPTER_TWO = Providers::ItemFromSupplierTwo
def get_item(item)
id = ItemDetail.new(item)
%w(ADAPTER_ONE ADAPTER_TWO).each do |provider|
item_detail = provider.new(id)
break if item_detail.valid?
end
item_detail
end
Your problem is that you aren't making an array that contains the constants' values; you're making an array with the strings "ADAPTER_ONE" and "ADAPTER_TWO". The %w() syntax always makes an array of strings — it doesn't resolve variable names.
What you want is to change your get_item code to something like this:
def get_item(item)
id = ItemDetail.new(item)
[ADAPTER_ONE, ADAPTER_TWO].each do |provider|
item_detail = provider.new(id)
break item_detail if item_detail.valid?
end or nil # break automatically makes the block return the value you break with
end
As an aside, personally, I think I'd rewrite it like this:
def get_item(item)
id = ItemDetail.new(item)
[ADAPTER_ONE, ADAPTER_TWO].map {|provider| provider.new(id) }.find &:valid?
end
Yup you have an array of strings not constants but if you want to go down that road in using classes from strings well it will be nice if you look at http://blog.sidu.in/2008/02/loading-classes-from-strings-in-ruby.html#.UuGdmGQ1i2w .Maybe it is not directly related to your problem but it is a good read.

Can I use methods for String class within the String class in Ruby?

My new method for a string object in ruby is supposed to return a hash of the count of each character within a string (loaded in from a .txt file) and I am probably trying to go about it the easy way, however I can't seem to make it work without passing the object. I was wondering if there was a way to do this without passing a string. Any help would be appreciated.
Here is my code
class String
def frequency
Object.downcase
Object.gsub("\n", " ")
h = {}
h["A:"] = Object.count('a')
h["B:"] = Object.count('b')
h["C:"] = Object.count('c')
h["D:"] = Object.count('d')
h["E:"] = Object.count('e')
h["F:"] = Object.count('f')
h["G:"] = Object.count('g')
h["H:"] = Object.count('h')
h["I:"] = Object.count('i')
h["J:"] = Object.count('j')
h["K:"] = Object.count('k')
h["L:"] = Object.count('l')
h["M:"] = Object.count('m')
h["N:"] = Object.count('n')
h["O:"] = Object.count('o')
h["P:"] = Object.count('p')
h["Q:"] = Object.count('q')
h["R:"] = Object.count('r')
h["S:"] = Object.count('s')
h["T:"] = Object.count('t')
h["U:"] = Object.count('u')
h["V:"] = Object.count('v')
h["W:"] = Object.count('w')
h["K:"] = Object.count('x')
h["Y:"] = Object.count('y')
h["Z"] = Object.count('z')
return h
end
end
Sounds like you are talking about self, which is the ruby keyword that refers to the current object. Note that self is implied if you just call the method. So to use your example
class String
def frequency
count('a')
end
end
would return the number of as in the string
"asdfa".frequency #=> 2
Just a note, but your current method is very repetitive, and you might want to think about taking advantage of a loop to reduce the amount of code. Also you are not counting capital letters :)
Rather than a very long, un-DRY method that iterates your object 26 times, how about using some Ruby:
def frequency
Hash[downcase.gsub(/[^a-z]/,'').chars.group_by(&:to_s).map{|char, group| ["#{char.upcase}:", group.size]}]
end
You can break this apart onto separate lines if you find it easier to read (and to look up the methods in the API [1]):
def frequency
intermediate_variable = downcase
intermediate_variable = intermediate_variable.gsub(/[^a-z]/,'') # only keep a-z characters
intermediate_variable = intermediate_variable.chars.group_by(&:to_s) # split the string into its component characters and then group that array by the characters (run this on an array to see what it does :-) could also have written it `.group_by{|e|e}`
intermediate_variable = intermediate_variable.map{|char, group| ["#{char.upcase}:", group.size]} # map the array of grouped characters into an array of character and counts (formatting the 'character' how you would like your hash key configured
Hash[intermediate_variable] # make a hash of the characters and their counts
end
[1] http://ruby-doc.org/core-2.0.0/Enumerable.html http://ruby-doc.org/core-2.0.0/String.html
Here is the version I used which is a complete copy of the Rosetta Letter Frequency:
#!/usr/bin/env ruby
def letter_frequency(string)
freq = Hash.new(0)
string.each_char.lazy.grep(/[[:alpha:]]/).map(&:upcase).each_with_object(freq) do |char, freq_map|
freq_map[char] += 1
end
end
In ruby you can just open the class and add the method, like:
class String
def my_method
my_method_code
end
end
Then you just call the method string.my_method. However in your case I would rather use a Ruby module. Here is a code sample, very similar to a class but cleaner imho:
#!/usr/bin/env ruby
module MyString
def self.letter_frequency(string)
freq = Hash.new(0)
string.each_char.lazy.grep(/[[:alpha:]]/).map(&:upcase).each_with_object(freq) do |char, freq_map|
freq_map[char] += 1
end
return freq
end
end
p MyString.letter_frequency('absd')
Modules are more suited for adding your own classes into projects avoiding name colliding and creating mixins.
I would just create a hash like this:
class String
def frequency
chars.each_with_object(Hash.new(0)) do |char, h|
h["#{char.upcase}:"] += 1 if char[/[[:alpha:]]/]
end
end
end

Setting variable A with name stored in variable B

I have the following two variables:
a = 1;
b = 'a';
I want to be able to do
SOMETYPEOFEVALUATION(b) = 2;
so that the value of variable a is now set to 2.
a # => 2
Is this possible?
Specifically, I am working with the Facebook API. Each object has a variety of different connections (friends, likes, movies, etc). I have a parser class that stores the state of the last call to the Facebook API for all of these connections. These states are all named corresponding to the the GET you have to call in order to update them.
For example, to update the Music connection, you use https://graph.facebook.com/me/music?access_token=... I store the result in a variable called updated_music. For books, its updated_books. If I created a list of all these connection type names, I ideally want to do something like this.
def update_all
connection_list.each do |connection_name|
updated_SomeTypeOfEvalAndConcatenation(connection_name) = CallToAPI("https://graph.facebook.com/me/#{connection_name}?access_token=...")
end
end
Very new to both Rails and StackOverflow so please let me know if there is a better way to follow any conventions.
Tried the below.
class FacebookParser
attr_accessor :last_albums_json,
def update_parser_vars(service)
handler = FacebookAPIHandler.new
connections_type_list = ['albums']
connections_type_list.each do |connection_name|
eval "self.last_#{connection_name}_json = handler.access_api_by_content_type(service, #{connection_name})['data']"
end
#self.last_albums_json = handler.access_api_by_content_type(service, 'albums')['data']
end
end
And I get this error
undefined local variable or method `albums' for #<FacebookParser:0xaa7d12c>
Works fine when I use line that is commented out.
Changing an unrelated variable like that is a bit of a code smell; Most programmers don't like it when a variable magically changes value, at least not without being inside an enclosing class.
In that simple example, it's much more common to say:
a=something(b)
Or if a is a more complex thing, make it a class:
class Foo
attr_accessor :a
def initialize(value)
#a = value
end
def transform(value)
#a = "new value: #{value}"
end
end
baz = "something"
bar = Foo.new(2)
bar.a
=> 2
bar.transform(baz)
bar.a
=> "new value: something"
So while the second example changes an internal variable but not through the accessor, at least it is part of an encapsulated object with a limited API.
Update Ah, I think the question is how do do like php's variable variables. As mu suggests, if you want to do this, you are probably doing the wrong thing... it's a concept that should never have been thought of. Use classes or hashes or something.
how about
eval "#{b}=2"
and with instance variables you can also do instance_variable_set("#name", value)
EDIT:
you can also use send method if you have a setter defined(and you have), try this:
class FacebookParser
attr_accessor :last_albums_json,
def update_parser_vars(service)
handler = FacebookAPIHandler.new
connections_type_list = ['albums']
connections_type_list.each do |connection_name|
send("last_#{connection_name}_json=",
handler.access_api_by_content_type(
service, connection_name)['data']))
end
end
end
problem with your original code is that
eval ".... handler.access_api_by_content_type(service, #{connection_name})"
would execute
... handler.access_api_by_content_type(service, albums)
# instead of
... handler.access_api_by_content_type(service, 'albums')
so you had to write
eval ".... handler.access_api_by_content_type(service, '#{connection_name}')" <- the quotes!
this is why people usually avoid using eval - it's easy to do this kind of mistakes
These sort of things are not usually done using local variables and their names in Ruby. A usual approach could include hashes and symbols:
data = Hash.new
data[:a] = 1 # a = 1
b = :a # b = 'a'
and then, later
data[b] = 2 # SOMETYPEOFEVALUATION(b) = 2
data[:a] # => 2

Ruby: How do I change the value of a parameter in a function call?

How would I impelement a function, in ruby, such as the following?
change_me! (val)
update:
What I set out to do was this:
def change_me! (val)
val = val.chop while val.end_with? '#' or val.end_with? '/'
end
This just ended up with....
change_me! 'test#///' => "test#///"
You're thinking about this the wrong way around. While it may be possible to do this in Ruby, it would be overly complicated. The proper way to do this would be:
val.change_me!
Which, of course, varies depending on the class of what you want to change. The point is that, by convention, the methods with '!' affect the class instance on which they're called.
So...
class Changeable
def initialize var
#var = var
end
def change_me! change=1
#var += change
end
end
a = Changeable.new 5 # => New object "Changeable", value 5
a.change_me! 6 # => #var = 7
a.change_me! # => #var = 8
Hope this helps a bit..
You want to do this:
def change_me(val)
val.replace "#{val}!"
end
This replaces the value with a new one. But trust me: You don't usually want to design your ruby code in such a way. Start thinking in objects and classes. Design your code free of side-effects. It will save a whole lot of trouble.
What kind of object is val and how do you want to change it? If you just want to mutate an object (say, an array or a string), what you are asking for works directly:
def change_me!(me)
me << 'abides'
end
val = %w(the dude)
change_me!(val)
puts val.inspect
val = "the dude "
change_me!(val)
puts val.inspect

Resources