I'm creating some tests and I would like to create an array of objects passing different parameters to the constructor, is there a smart way of doing that?
For example, suppose I have this code:
foos = 3.times.map{ Foo.new("a") }
This code would give me an array with 3 foo's, but all of them initialized with the "a" parameter.
I would rather have an array where the first one is initialized with "a", the second with "b" and the third with "c", like it would if did this:
foos = [Foo.new("a"),Foo.new("b"),Foo.new("c")]
Is there a smarter way of doing this?
Try this one
%w(a b c).map { |s| Foo.new(s) }
In case you don't know
%w(a b c)
is a shorter way to say
["a", "b", "c"]
Going a bit deeper on your question, I'm going to suggest you two libraries: Factory Girl and Faker. None is Rails nor Rspec specific.
With Factory Girl you can define factories to create test data and with Faker you can associate randomized data to your factories.
I'm suggesting these two libraries because creating test data for simple use cases is easy, but it starts to get very complicated when you have more complex relationships between objects.
Here's an example to get you started:
class Foo < Struct.new(:bar)
end
FactoryGirl.define do
factory :foo do
bar Faker::Number.digit
end
end
FactoryGirl.build_list(:foo, 3)
=> [#<struct Foo bar=3>, #<struct Foo bar=2>, #<struct Foo bar=7>]
FactoryGirl.build(:foo)
=> #<struct Foo bar=5>
FactoryGirl.build(:foo, bar: 2)
=> #<struct Foo bar=2>
Here are more examples on how powerful Factory Girl is. Just remember that you need to use the build* methods instead of create* methods, because create* methods also call on save to persist the data when using it with a persistence layer.
Just to offer an alternative:
arr = ['a', 'b', 'c']
Array.new(arr.size) { |i| Foo.new arr[i] }
#=>
#[#<Foo:0x000000025db798 #arg="a">,
# #<Foo:0x000000025db748 #arg="b">,
# #<Foo:0x000000025db6d0 #arg="c">]
Related
In Elixir there is a great pipeline operator working like this:
"hello, world!"
|> String.split(" ")
|> Enum.map(&String.capitalize/1)
|> Enum.join
In Ruby we can use similar syntax:
"hello, world!"
.split(" ")
.map(&:capitalize)
.join
It works only when have all these methods defined for a object itself. If need to call some local method we should use something like:
.map { |el| URI.parse(el) }
But what if we want to make some collection processing (not a single element), for example GZIP Compression:
chars = text
.downcase
.chars
compressed = GZipped.new(chars).bytes
But chain is broken!
I've found some links, but looks not awesome:
pipe_envy - UGLY! no collections
chainable_methods - no collections
How to use chainable_methods
piperator - much better! But looks heavy
In my opinion it would be great to have something like:
text
.split
.pipe(URI.method(:parse))
.map(&:to_s)
.join
.pipe(GZIPped)
.pipe(Base64.method(:encode))
What is the best way to build such pipes in Ruby?
Update 1
Here is an example
class Dedup
def initialize(obj)
#obj = obj
end
def each
Enumerator.new do |y|
prev = nil
#obj.each do |el|
if el != prev
y << el
prev = el
end
end
end
end
end
expect(
"1 1 1 2 2 3"
.split
.then { |obj| Dedup.new(obj).each }
.to_a
).to eq [1, 2, 3]
This chaining looks ugly and unreadable.
Comparing to:
expect(
"1 1 1 2 2 3"
.split
.pipe(Dedup)
.to_a
).to eq [1, 2, 3]
There's already a method like this, at least starting from Ruby 2.5 - yield_self, aliased as then in Ruby 2.6. You can use & operator with any object responding to to_proc to pass it instead of a block.
text
.split
.map(&URI.method(:parse)) # URI#parse expects a string, not an array
.map(&:to_s)
.join
.then(&GZIPped) # not sure what GZIPped is - I'll assume it has .to_proc method
.then(&Base64.method(:encode))
(I should probably mention that the code above will not actually work and honestly I have no clue what it would suppose to do - why would split a string, convert them to into urls and then back again to strings? The only thing this would to is to raise id one of the substrngs were not a valid string? But then you try to read resulting string as a gzipped file... I'm assuming I misunderstood something in your code)
More advanced stuff - one thing that I quite like in elixir was an option to chain methods together with remaining arguments. This also can be simulated in ruby, but requires a bit of work and good think whether it's worth it:
module MyMath
module_function
UNDEFINED = Object.new
def add(a, b = UNDEFINED)
if b == UNDEFINED
return ->(num) { add(a, num) }
end
a + b
end
end
MyMath.add(2,5) #=> 7
[1,2,5,9].map(&MyMath.add(5)] #=> [6,7,10,14]
I am writing code in Ruby.
Say I have an array like this:
myarray = [1,2,3,4,5]
I want to change the code of the Array class so that myarray[7] would return "out of bounds" instead of nil.
I don't want to make so that I check if something is out of bounds and then return "out of bounds", I want to implement this in Array directly.
Is this possible? Do I have to extend the Array class?
I'm Not sure how to start, very new to ruby. My initial thoughts were overriding a method in the Array class, but I'm not sure how to do that when I don't know what method is being used in myarray[i].
Suppose
arr = [1,2,3,4,5]
Let's start by examining the doc Array#[]. As seen, that method can have a single integer argument:
arr[2] #=> 3
arr[-2] #=> 4
arr[5] #=> nil
arr[-7] #=> nil
arr['x'] #=> TypeError (no implicit conversion of String into Integer)
or it may have two integer arguments:
arr[1,3] #=> [2, 3, 4]
arr[-3,2] #=> [3, 4]
arr[3,5] #=> [4, 5]
arr[7,4] #=> nil
arr[2,'x'] #=> TypeError (no implicit conversion of String into Integer)
or it may have a range of integers as an argument:
arr[1..3] #=> [2, 3, 4]
arr[1...3] #=> [2, 3]
arr[-5..-2] #=> [1, 2, 3, 4]
arr[3..9] #=> [4, 5]
arr[7..10] #=> nil
arr[1..'x'] #=> ArgumentError (bad value for range)
As I understand, you wish to alter Array#[] in such way that its behavior is unchanged except for the case when its argument is a single integer representing an offset into the array that is out-of-bounds.1. That could be done by writing the method in a module and then prepend that module to the class Array with the method Module#prepend.
module NewSlice
def [](*args)
arg1, arg2 = args
if arg2.nil? && arg1.is_a?(Integer) &&
(arg1 > self.size-1 || arg1 < -self.size)
"out of range"
else
super
end
end
end
Array.prepend(NewSlice)
arr[2] #=> 3
arr[-2] #=> 4
arr[5] #=> "out of range"
arr[-7] #=> "out of range"
arr['x'] #=> TypeError (no implicit conversion of String into Integer)
arr[1,3] #=> [2, 3, 4]
arr[-3,2] #=> [3, 4]
arr[3,5] #=> [4, 5]
arr[7,4] #=> nil
arr[2,'x'] #=> TypeError (no implicit conversion of String into Integer)
arr[1..3] #=> [2, 3, 4]
arr[1...3] #=> [2, 3]
arr[-5..-2] #=> [1, 2, 3, 4]
arr[3..9] #=> [4, 5]
arr[7..10] #=> nil
arr[1..'x'] #=> ArgumentError (bad value for range)
We see that
Array.ancestors
#=> [NewSlice, Array, Enumerable, Object, Kernel, BasicObject]
showing that super in NewSlice#[] calls the original method Array#[] after NewSlice has been prepended to Array. Since super has no arguments all the arguments that were passed to the new Array#[] method ( defined in NewSlice) are passed to the original Array#[].
Aliases were commonly used in Ruby prior to the introduction of Module#prepend in Ruby v2.0. They are still used but their importance has diminished greatly since prepend has become available.
If nil is to be replaced by "out of bounds" in all cases where Array#[] returns nil, write
module NewSlice
def [](*args)
super || "out of bounds"
end
end
Array.prepend(NewSlice)
arr[5] #=> "out of range"
arr[-7] #=> "out of range"
arr[7,4] #=> "out of range"
arr[7..10] #=> "out of range"
1. Whether it is advisable to modify core methods such as Array#[] is a separate question, but it can result in pain and suffering.
TL;DR
This is an interesting question about how to monkeypatch core Ruby classes like Array. However, I'd be remiss if I didn't point out that it involves some hijinks that seem unnecessary, since you could get the same behavior with a simple logical OR operator like:
[1, 2, 3][5] || 'out of bounds'
#=> "out of bounds"
Still, if you want to change the behavior of a core class then it can certainly be done.
Re-Open the Core Class
In Ruby, almost everything that isn't a keyword is a method. Pragmatically, Array::[] is really just a method for indexing into an array object, with a little syntactic sugar from the interpreter to allow its arguments to be placed inside the brackets. Because it's just an instance method defined by the Array class, it can be modified in various ways such as re-opening the core class to modify the method, or by creating a singleton method on the instance. The first is conceptually simpler, but singleton methods are probably the safest solution for real-world programming.
You can re-open the Array class and redefine Array::[] as follows:
class Array
# save off a copy of the existing method; we still need it
alias_method :'old_[]', :'[]' unless defined? Array::old_[]
# pass all arguments to the old method, but return a string
# when the result is falsey
def [] *args
send("old_[]", *args) || 'out of bounds'
end
end
You can then demonstrate that it works like a regular Array, but with your expected behavior:
a = [10, 20, 30]
a[0] #=> 10
a[0,2] #=> [10, 20]
a[100] #=> "out of bounds"
Add a Singleton Method
While the code above works, it's just generally a bad idea to monkeypatch a core class like Array. If you don't want to encapsulate this behavior in a subclass (e.g. class MyArray < Array), then you should use refinements (using Module#refine) or singleton methods like the following to reduce the scope of your changes. For example:
a = [10, 20, 30]
class << a
alias_method :'old_[]', :'[]' unless self.respond_to? 'old_[]'
def [] *args
send("old_[]", *args) || 'out of bounds'
end
end
By limiting the scope of our change to a single instance, we're much less likely to cause serious or unrecoverable problems with core classes. A lot of things can go wrong in a Ruby program if a core class like Array starts returning truthy values (e.g. a String value such as out of bounds) rather than a falsey value like nil, so caveat emptor!
If you really want to break existing code (standard classes and ruin even methods of the current Array class itself, which rely on the current behaviour), you can of course monkeypatch it - open it and redefine the method in any way you like, as has been outlined in various answers to this question.
A more sane approach would be to subclass Array, i.e.
class BoundCheckingArray < Array
# redefined the methods according to your taste.
end
Of course this would violate the Liskov Substitution Principle.
Another approach will be to define a complete separate class and use Delegator to avoid rewriting everything from scratch, by delegating methods you did not rewrite explicitly, to the underlying array.
In both cases you need to be aware, that those methods in your BoundCheckingArray, which you did not explicitly rewrite and hence are taken from BoundCheckingArray, will stop working, if they assume that they can safely access the array outside of the bounds.
Another possibility would be to not modify existing Array methods, but simply add bound checking setters and getters, i.e. with monkeypatching:
class Array
def at_checked(i)
fail "out_of_bounds" if i >= size || i < -size
at(i)
end
def set_checked(i, new_value)
# analoguous
end
end
Of course this approach would also work with subclassing (now adhering to Liskov's principle, because you just added methods) and delegating. Note that I coped explicitly with negative index values, assuming that you would like to use this feature in your bound checking array too.
You may know classes in Ruby are defined like:
class Foo
def foo
'foo'
end
end
Foo.new.foo # calls instance method `foo` on the instance of `Foo`
# => 'foo'
Classes and modules in Ruby can be "reopened" for re-definition (although it's a bit frowned upon -- makes it difficult to track code b/c code can be in either/any place):
class Foo
def bar
'bar'
end
def foo
'overwrite foo'
end
end
f = Foo.new
f.bar
# => 'bar'
f.foo
# => 'overwrite foo'
This means you can also do:
class Array
# re-define anything you want
end
# alternatively:
Array.class_eval do
# the same as the above, in most ways
end
Even though it's even less recommended to modify existing methods of core Ruby classes. Not only does it break assumptions other Ruby developers may have of the code (i.e. misleading code), its global effect will likely break behaviors in other libraries, etc.
I have an array of type "Option".
The class Optioncontains an element optionDetail.
The class optionDetail contains elements of detailTraits, which is an [String] with each string being called the detailTraitName.
So my structure of getting the detailTraits looks like Option -> optionDetail -> detailTraitswhich would return me [String], or Option -> optionDetail -> detailTraits -> detailTraitName, which would return me just one String
I would like to match up the detailTraits array with another array, named selectedDetails, which is an [String] and find the elements in which all of the selectedDetails are contained inside of the detailTraits. I then want to return all of the Option in which this situation is true.
For example, if my selectedDetails array contains ["A", "B"], and I have one detailTraits array that has ["A","C"] and one that has ["A"] and one that has ["A", "B", "C"], I just want to return the option which had detailTraits of ["A", "B", "C"]
My current code looks like the following:
newOptions = option.filter({ $0.optionDetail?.detailTraits.filter({ selectedDetails.contains($0.detailTraitName ?? "") }).count == selectedDetails.count })
Is there a better way to do this? This algorithm seems pretty inefficient since It's probably in the order of magnitude of N^3, but I can't think of a better way to look through an array of arrays and match it to another array.
Thank you!
You can optimise this by first filtering by comparing count on selectedDetails and detailTraits, and then comparing actual values. This way options set would be reduced to only those detailTraits having exact same count. For example you would only need to compare the string values with array containing exact 3 items (if selectedDetails is ["A", "B", "C"]), completely avoiding one iteration in loop.
Hope this helps
I'm using Ruby 2.4. If I want to extract certain elements from an array at indexes I specify in another array, I can do
2.4.0 :012 > arr.values_at(*indexes)
=> ["a", "e", "g"]
But if I have an array of arrays, how would apply the above to each array in the array of arrays? I tried this
2.4.0 :014 > arr_of_arrays.map( &values_at(*indexes) )
NoMethodError: undefined method `values_at' for main:Object
Also I would like the result to be a new object, as opposed to modifying the original object in place.
You can't use symbol to proc with a parameter.
arr_of_arrays.map { |a| a.values_at(*indexes) }
One possible workaround is modifying the Symbol class, but this practice is highly frowned upon.
class Symbol
def with(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
With this you can pass parameters.
Before judging me for an irrelevant question, I'll safeguard myself, that I know << is a bitwise operator. However, in both cases (array, string) it operates as just adding / concatenating values.
Any tip for clarifying whether there's difference if we use array<
Thanks
However, in both cases (array, string) it operates as just adding /
concatenating values.
It makes no difference "result-wise" - in both cases you get a value containing both operands (in that or another form).
The difference shows up in the way operands are impacted:
one performs in-place mutating
second is simply concatenating without changing the original strings.
Consider the following examples:
a = 'a'
b = 'b'
Now:
# concatenation - no changes to original strings
a + b #=> "ab"
a #=> "a"
b #=> "b"
Whereas:
# mutation - original string is changed in-place
a << b #=> "ab"
a #=> "ab"
Same goes with arrays:
# concatenation - no changes to original arrays
a = ['a'] #=> ["a"]
b = ['b'] #=> ["b"]
a + b #=> ["a", "b"]
a #=> ["a"]
b #=> ["b"]
# mutation - original array is changed in-place
a << b #=> ["a", ["b"]]
a #=> ["a", ["b"]]
As to Array#push and Array#<< - they do the same thing.
Firstly, << is not a bit-wise operator. Nor is :<< or "<<". The first is not a Ruby object or keyword. :<< is a symbol and "<<" is a string. Fixnum#<<, by constrast, is a bit-wise operator, implemented as an instance method on the class Fixnum.
You may argue that it's obvious what you meant, but it's not. Many classes have instance methods of the same name that are unrelated. Several classes, for example, have methods called "<<", "+", "size", "replace", "select", "each" and on and on. The only way to speak meaningfully of an instance method, therefore, is to also give the class on which it is defined.
What is an "operator" in Ruby? Frankly, I don't know. I've never found a definition. Whatever it is, however, most of them are implemented as instance methods.
Many of Ruby's core methods have names that may seem unusual to those coming from other languages. Examples are "<<", "+" and "&". The important thing to remember is that these are perfectly-valid names. Let's try using them as you would any other method:
[1,2,3].<<(4) #=> [1, 2, 3, 4]
"cat".+("hat") #=> "cathat"
[1,2,3].&([2,4]) #=> [2]
The head Ruby monk knew that his disciples would prefer to write these as follows:
[1,2,3] << 4
"cat" + "hat"
[1,2,3] & [2,4]
so he said "OK", which when translated from Japanese to English means "OK". He simply designed the Ruby parser so that when it saw the later form it would convert the expression to the standard form before parsing it further (or something like that). This has come to be called syntactic sugar. (Syntactic sugar doesn't allow you to write "cat" concat "hat", however--it only applies to names that are made up of symbols.)
My point with Ruby's operators is that most are implement with garden-variety methods, albeit methods with odd-sounding names. Yes, there are methods String#+ and Array#+ but they are completely unrelated to each other. If they were instead named String#str_add and Array#arr_add and used like so:
"abc".str_add("def")
[1,2,3].arr_add([2,4])
you probably wouldn't be asking the question you've raised.