Crystal behavior with variables assignement - behavior

I create this class:
1. class Component
2.
3. getter :content, :htm_options, :options
3.
4. #html_options : Hash(Symbol | String, Symbol | String)
5. #options : Hash(Symbol | String, Symbol | String)
6. #content : String
7.
8. def initialize(c = nil, o = nil, h = nil, block = nil)
9. if false #!block.nil?
10. #html_options = o unless o.nil?
11. #options = c unless c.nil?
12. context = eval("self", block.binding)
13. #content = context.capture(&block)
14. else
15. if c.is_a?(Hash)
16. #html_options = o unless o.nil?
17. #options = c unless c.nil?
18. else
19. #html_options = h unless h.nil?
20. #options = o unless o.nil?
21. #content = c unless c.nil?
22. end
23. end
24. end
25. end
Component.new("My text")
I have an error :
in src/ui_bibz/ui/core/component.cr:11: instance variable '#options' of Component must be Hash(String | Symbol, String | Symbol), not String
#options = c unless c.nil?
I don't understand this behavior because I do not pass in the if condition. I'd like assign #htm_options, #options, #content according to several conditions.
Is it possible to declare a variable that has already been declared once?

I'm afraid the compiler is sometimes not smart enough to detect which paths are taken and which are not, when checking for types. So, in line 11, the compiler sees that you are attempting to assign a String to a variable previously defined as a Hash, and fails.
As for your question, I'm afraid it is not possible to re-declare an instance variable. If you do want #options to hold both a String or a Hash, you can declare it to be a Union of Hash and String.

Crystal is typed and this is a good thing, but for my case, i'd like to redefine my variables. My code works like a "link_to" in ruby. A component must be write in different ways. Sometimes, content will be filled by variable, sometimes by block. Initialize method will be filled by different ways. There's a way to redefined variables by macro or by an other solution?
class Component < UiBibz::Ui::Base
getter :content, :htm_options, :options
def initialize(c = nil, o = nil, h = nil, block = nil)
if !block.nil?
html_opts, opts = o, c
context = eval("self", block.binding)
#content = context.capture(&block)
else
if c.is_a?(Hash)
html_opts, opts = o, c
else
html_opts, opts, cont = h, o, c
end
end
#html_options = html_opts || {} of Symbol => Symbol | String
#options = opts || {} of Symbol => Symbol | String
#content = content
end
end
# 1 way
Component.new("My content", { :option_1 => true }, { :class => "red" })
# 2 way
Component.new({ :option_1 => true }, { :class => "red" }) do
"My content"
end
Error:
in src/ui_bibz/ui/core/component.cr: instance variable '#options' of Component must be Hash(Symbol, String | Symbol), not (Hash(Symbol, String | Symbol) | String)
#options = opts || {} of Symbol => Symbol | String

Related

Order of optional parameters in initializer

Why am I getting the following error
a.rb:4: syntax error, unexpected '=', expecting ')'
...alize(a = "default val", b, c = [])
a.rb:18: syntax error, unexpected `end', expecting end-of-input
on the following code
class A
attr_reader :a, :b, :c
def initialize(a = "default val", b, c = [])
#a = a
#b = b
#c = c
end
def self.open(a)
args = {
b: "2",
c: [1, 2, 3]
}.values
A.new(a, *args)
end
end
when trying to call a property
a2 = A.open("something")
p a2.a
Removing the last default value from initializer =[] solves the problem. Reordering the arguments so the parameters with default values go at the end of initialize helps too.
def initialize(a = "default val", b, c)
or
def initialize(b, c=[], a = "default val") and A.new(*args, a) (but I suppose this is wrong)
As I remember there was a rule about ordering of optional params.
If you define optional parameters before AND after mandatory parameters, in some cases it will be impossible to decide how a goven list or arguments should map to the defined parameters.
In your case, when defining this method:
class A
def initialize(a = "default val", b, c = [])
#...
end
end
How would you handle this when giving two arguments,. i.e.
A.new 'hello', 'world'
You could then assign
a = 'hello'
b = 'world'
c = []
but you could equally set
a = 'default val'
b = 'hello'
c = 'world'
Given this unambiguity, Ruby rejects those constructs. You thus have to define all optional parameters either at the front or the back of your parameter list, while it is commonly accepted standard to define optional arguments only at the end.
If you want to be more specific about which arguments should be set with a large number of optional parameters, you can also use keyword arguments. Since you have to specify the name of the arguments when calling the method here, the order of mandatory and optional keyword arguments doesn't matter.

Assign variable only if not nil

I have #obj.items_per_page, which is 20 at the beginning, and I want the method below to assign value to it only if many_items is not nil:
def fetch_it_baby (many_items = nil)
#obj.items_per_page = many_items
With the code above, even if many_items is nil, #obj.items_per_page remains at 20. Why? And is that "good" coding? Shouldn't I use something like
#obj.items_per_page = many_items || #obj.items_per_page
Or is there a third way? I don't feel completely comfortable with either way.
The style I generally see looks like this:
#obj.items_per_page = many_items if many_items
This uses the inline conditional, while avoiding negative or double-negative conditions.
I suggest the following as it makes it clear that you have a default value for the assignment in case the caller did not specify many_items in the call:
def function(argument = nil)
variable = argument || 20
...
end
However, since you specified that the assignment takes places only if the value is not nil then you'll need to check for the nil value otherwise you will miss the assignment if the value was false. If you really need that case then the solution is more long-winded:
def function(argument = nil)
variable = argument.nil? ? 20 : argument
...
end
You can use &&= (in the same way as ||= is used to assign only if nil or false)
> a = 20 # => 20
> a &&= 30 # => 30
> a # => 30
> a = nil # => nil
> a &&= 30 # => nil
> a = false # => false
> a &&= 30 # => false
> a = {} # => {}
> a &&= 30 # => 30
remember though
> a = 30 # => 30
> a &&= nil # => nil
> a &&= false # => nil
> b &&= 3 # => nil
Even if many_items is nil #obj.items_per_page remains at 20
That sounds like whatever class #obj is has a custom modifier method items_per_page= that only updates the value if the new value is not nil. This is not standard Ruby. For example, given this definition:
class Demo
attr_accessor :items_per_page
end
I get this behavior:
irb(main):005:0> demo = Demo.new #=> #<Demo:0x007fb7b2060240>
irb(main):006:0> demo.items_per_page = 20 #=> 20
irb(main):007:0> demo.items_per_page #=> 20
irb(main):008:0> demo.items_per_page = nil #=> nil
irb(main):009:0> demo.items_per_page #=> nil
As for your example, I would probably write it this way:
#obj.items_per_page = many_items unless many_items.nil?
For Rails you can also use presence as described here
region = params[:state].presence || params[:country].presence || 'US'
new-alexandria's answer is my go-to, but another "third way" would be to use a ternary:
class Demo
attr_accessor :items_per_page
end
many_items = 100
#obj = Demo.new
#obj.items_per_page = 20 #=> 20
#obj.items_per_page = !many_items.nil? ? 30 : nil #=> 30
I am using Rails and I have a similar need.
You can define a method on your model:
class Gift < ApplicationRecord
def safe_set(attribute, value)
return if value.nil?
send("#{attribute}=", value)
end
end
So you can do
g = Gift.new
g.colour = 'red'
g.safe_set(:colour, nil)
g.colour -> 'red'
g.safe_set(:colour, 'green')
g.colour -> 'green'
We have one more method in rails that can help to remove values that are nil.
compact
This method can be used with an array, hash. Returns the same data removing all values that are nil
Eg :
array.compact
Further reference:
https://apidock.com/ruby/v1_9_3_392/Array/compact
If many items is a variable the if/unless versions above are the best. But if many_items is actually a method you don't want to evaluate multiple times I find the following useful.
#obj.items_per_page = many_items.presence || #obj.items_per_page
This isn't quite what you want since it won't assign empty strings or other non-nil but non-present values but most of the time I want to do this kind of thing this works for me (it does create a self-assignment when the value is nil/non-present so only use it if your setters are idempotent).

Setting default values

I am learning ruby and I am specifically playing with OOPS in it. I am trying to write equivalent of this PHP code in ruby
class Abc {
$a = 1;
$b = 4;
$c = 0;
function __constructor($cc) {
$this->c = $cc
}
function setA($v) {
$this->a = $v
}
function getSum() {
return ($this->a + $this->b + $this->c);
}
}
$m = new Abc(7);
$m->getSum(); // 12
$m->setA(10);
$m->getSum(); // 21
I am trying to write equivalent of above PHP to ruby.
Please note my goal is to have default values of soem of the class variable and if I want to override it, then I can do it by calling getter/setter method.
class Abc
attr_accessor :a
def initialize cc
#c = cc
end
def getSum
#???
end
end
I don't like to do
Abc.new(..and pass value of a, b and c)
My goal is to have default values, but they can be modified by instance, if required.
class Abc
attr_accessor :a, :b, :c
def initialize a = 1, b = 4, c = 0
#a = a
#b = b
#c = c
end
end
This will accept 1, 4, and 0 respectively as default values, but they can be overridden by passing in parameters.
So if you do example = Abc.new without paramaters it will have default values of 1,4,0 but you could do:
example2 = Abc.new 5, 5
without passing a value for c and you'd have values of a = 5 and b = 5 with by default c = 0 still.
More broadly, in your Ruby code examples above, you are using brackets where not needed. a def method_name begins a block, and a end will finish it. They serve in place of how brackets are traditionally used in other languages. So for your method getSum you can simply do
def get_sum
#your_code
end
Also, note def getSum (camelCase) would typically be def get_sum (snake_case) in Ruby. Also note in the examples I give above that parenthesis are dropped. They are not needed in Ruby.

Merge Ruby arrays

I have a few arrays of Ruby objects of class UserInfo:
class UserInfo
attr_accessor :name, :title, :age
end
How can I merge these arrays into one array? A user is identified by its name, so I want no duplicate names. If name, title, age, etc. are equal I'd like to have 1 entry in the new array. If names are the same, but any of the other details differ I probably want those 2 users in a different array to manually fix the errors.
Thanks in advance
Redefine equality comparison on your object, and you can get rid of actual duplicates quickly with Array#uniq
class UserInfo
attr_accessor :name, :title, :age
def == other
name==other.name and title==other.title and age==other.age
end
end
# assuming a and b are arrays of UserInfo objects
c = a | b
# c will only contain one of each UserInfo
Then you can sort by name and look for name-only duplicates
d = c.sort{ |p,q| p.name <=> q.name } #sort by name
name = ""
e = []
d.each do |item|
if item.name == name
e[-1] = [e[-1],item].flatten
else
e << item
end
end
A year ago I monkey patched a kind of cryptic instance_variables_compare on Object. I guess you could use that.
class Object
def instance_variables_compare(o)
Hash[*self.instance_variables.map {|v|
self.instance_variable_get(v)!=o.instance_variable_get(v) ?
[v,o.instance_variable_get(v)] : []}.flatten]
end
end
A cheesy example
require 'Date'
class Cheese
attr_accessor :name, :weight, :expire_date
def initialize(name, weight, expire_date)
#name, #weight, #expire_date = name, weight, expire_date
end
end
stilton=Cheese.new('Stilton', 250, Date.parse("2010-12-02"))
gorgonzola=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
irb is my weapon of choice
>> stilton.instance_variables_compare(gorgonzola)
=> {"#name"=>"Gorgonzola", "#expire_date"=>#<Date: 4910305/2,0,2299161>}
>> gorgonzola.instance_variables_compare(stilton)
=> {"#name"=>"Stilton", "#expire_date"=>#<Date: 4910275/2,0,2299161>}
>> stilton.expire_date=gorgonzola.expire_date
=> #<Date: 4910305/2,0,2299161>
>> stilton.instance_variables_compare(gorgonzola)
=> {"#name"=>"Gorgonzola"}
>> stilton.instance_variables_compare(stilton)
=> {}
As you can see the instance_variables_compare returns an empty Hash if the two objects has the same content.
An array of cheese
stilton2=Cheese.new('Stilton', 210, Date.parse("2010-12-02"))
gorgonzola2=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
arr=[]<<stilton<<stilton2<<gorgonzola<<gorgonzola2
One hash without problems and one with
h={}
problems=Hash.new([])
arr.each {|c|
if h.has_key?(c.name)
if problems.has_key?(c.name)
problems[c.name]=problems[c.name]<<c
elsif h[c.name].instance_variables_compare(c) != {}
problems[c.name]=problems[c.name]<<c<<h[c.name]
h.delete(c.name)
end
else
h[c.name]=c
end
}
Now the Hash h contains the objects without merging problems and the problems hash contains those that has instance variables that differs.
>> h
=> {"Gorgonzola"=>#<Cheese:0xb375e8 #name="Gorgonzola", #weight=250, #expire_date=#<Date: 2010-12-17 (4911095/2,0,2299161)>>}
>> problems
=> {"Stilton"=>[#<Cheese:0xf54c30 #name="Stilton", #weight=210, #expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>, #<Cheese:0xfdeca8 #name="Stilton", #weight=250,#expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>]}
As far as I can see you will not have to modify this code at all to support an array of UserInfo objects.
It would most probably be much faster to compare the properties directly or with a override of ==. This is how you override ==
def ==(other)
return self.weight == other.weight && self.expire_date == other.expire_date
end
and the loop changes into this
arr.each {|c|
if h.has_key?(c.name)
if problems.has_key?(c.name)
problems[c.name]=problems[c.name]<<c
elsif h[c.name] != c
problems[c.name]=problems[c.name]<<c<<h[c.name]
h.delete(c.name)
end
else
h[c.name]=c
end
}
Finally you might want to convert the Hash back to an Array
result = h.values
Here's another potential way. If you have a way of identifying each UserInfo, say a to_str method that prints out the values:
def to_str()
return "#{#name}:#{#title}:#{#age}"
end
You can use inject and a hash
all_users = a + b # collection of users to "merge"
res = all_users.inject({})do |h,v|
h[v.to_str] = v #save the value indexed on the string output
h # return h for the next iteration
end
merged = res.values #the unique users

Is it possible to have class.property = x return something other than x?

Let's say I have a Ruby class:
class MyClass
def self.property
return "someVal"
end
def self.property=(newVal)
# do something to set "property"
success = true
return success # success is a boolean
end
end
If I try and do MyClass.property=x, the return value of the whole statement is always x. It is a convention in a lot of C-based/inspired languages to return a boolean "success" value - is it possible to do this for a setter using the "equals syntax" in Ruby?
Furthermore - if this isn't possible, why not? Is there any conceivable downside to allowing an "equals setter" operation return a value?
One downside is that you would break the chained assignment semantics:
$ irb
irb(main):001:0> x = y = 3
=> 3
irb(main):002:0> p x
3
=> nil
irb(main):003:0> p y
3
=> nil
irb(main):004:0>
Consider:
x = MyClass.property = 3
Then x would take true if this worked as you had expected (right-associativity). That could be a surprise for people using your interface and used to the typical semantics.
You also got me thinking about parallel assignment, eg:
x, y = 1, 2
Apparently the return value from that expression is implementation specific... I guess I won't be chaining parallel assignments :)
Nice question!
Like Martin says, this would break assignment chaining.
The way ruby assignment methods are defined to work expands MyClass.property = 3 to the equivalent of (lambda { |v| MyClass.send('property=', v); v })[3] (not really, but this shows how chaining works). The return value of the assignment is always the value assigned.
If you want to see the result of your MyClass#property= method, then use #send:
irb> o = Object.new
=> #<Object:0x15270>
irb> def o.x=(y)
irb> #x = y+1
irb> puts "y = #{y}, #x = ##x"
irb> true
irb> end
=> nil
irb> def o.x
irb> puts "#x = ##x"
irb> #x
irb> end
=> nil
irb> o.x = 4
y = 4, #x = 5
=> 4
irb> o.x
#x = 5
=> 5
irb> o.send('x=', 3)
y = 3, #x = 4
=> true
However, the ruby way to do this is with exceptions - if something goes wrong during
the assignment, raise an exception. Then all invokers must handle it if something goes
wrong, unlike a return value, which can be easily ignored:
# continued from above...
irb> def o.x=(y)
irb> unless y.respond_to? :> and (y > 0 rescue false)
irb> raise ArgumentError, 'new value must be > 0', caller
irb> end
irb> #x = y + 1
irb> puts "y = #{y}, #x = ##x"
irb> end
=> nil
irb> o.x = 4
y = 4, #x = 5
=> 4
irb> o.x = 0
ArgumentError: new value must be > 0
from (irb):12
from :0
irb> o.x = "3"
ArgumentError: new value must be > 0
from (irb):13
from :0
irb> o.x
#x = 5
=> 5
I'm not a Ruby expert but I'd say no for that case I'm afraid. A property setter is solely there to set the value of a private field, not to have any side effects like returning result codes.
If you want that functionality then forget the setter and write a new method called TrySetProperty or something which tries to set the property and returns a boolean.

Resources