Use class constant as Ruby hash key - ruby-on-rails

I know I can use class as Hash key, but is it a good practice? Is there any drawbacks in terms of performance or testing?
{
SomeClassA: 'hash value',
AnotherClass: 'hash value'
}

{
SomeClassA: 'hash value',
AnotherClass: 'hash value'
}
Is actually equivalent to:
{
:SomeClassA => 'hash value',
:AnotherClass => 'hash value'
}
The keys are symbols. In the "new" literal hash syntax keys are just treated as literals which are cast into symbols (provided they are valid syntax).
To use constants, ranges or any other type of object you can dream up as keys you need to use hashrockets:
{
SomeClassA => 'hash value',
AnotherClass => 'hash value'
}
Is it a good practice?
Its a technique that could be used in few limited cases. For example to replace a series of if statements.
def foo(bar)
if bar.is_a? SomeClassA
'hash value'
end
if bar.is_a? AnotherClass
'another value'
end
end
def foo(bar)
{
SomeClassA => 'hash value',
AnotherClass => 'another value'
}[bar]
end
But I would rather use a case statement there anyways as its clearer in intent and more flexible.
Are there any drawbacks in terms of performance or testing?
Each hash you create would have keys that point to the exact same objects in memory just like if you where using symbols.
One big gotcha is that Rails monkey-patches const_missing to autoload files - when you reference a class name rails will load the file from the file system into memory. This is why you declare associations with:
class Foo < ApplicationRecord
belongs_to :bar, class_name: "Baz"
end
It lets Rails instead lazy load Baz when needed. You would do the same with the example above by:
def foo(bar)
{
'SomeClassA' => 'hash value',
'AnotherClass' => 'another value'
}[bar.name]
end

That hash is using symbols, not classes, as keys, but you can use a Class by doing
hash = { SomeClassA => "some value" }
I can't think of why it would be worse than using any other object because
Classes in Ruby are first-class objects---each is an instance of class Class
So
{ SomeClassA => "some value" }
is functionally equivalent to
{ "Some String" => "some value" }

What you have there are not class keys but symbols.
Try
class A
end
class B
end
hash = {A: "Some", B: "Code"}
hash.keys[0].class
=> Symbol
But still A.class => Class
In case of symbol performance this POST is great
Also you can check ruby documentation about user-defined class as hash keys

While you can do it, I can't think of a reason for using it. Using a symbol or string as the key is much more efficient as the program does not have to load the whole class. (a side note, as pointed by others, your example in fact is using a symbol key, and you'll need to use a hash rocket to use a class name as the key)

Related

The thing with Ruby on Rails and Colons

I used to be more of a hobby Java guy and try to switch to Ruby on Rails right now.
But I'm having some difficulties, believe it or not, I like braces and semicolons..gives some orientation.
But here's the question:
Right now I'm taking an online course in RoR and it occured to me, that I'm always wrong on how to work with symbols, hashes etc.
Example 1:
Let's take a look at this single line of code for example:
form_for(:session, :html => {class: "form-horizontal", role: "form"}, url: login_path)
And this is how I read it:
Method / Function name is
form_for
Parameters parsed to this method are:
:session, :html => {class: "form-horizontal", role: "form"}, url: login_path
Let's break up those into
:session
:html => {class: "form-horizontal", role: "form"}
url: login_path
How the heck should I know how to declare those parameters?
Why are :session and :html passend in as keys and url not?
Is the :html symbol a Hashmap-symbol?
Example:
In an model File you declare an n:m relationship like this (for example users <-> stocks)
has_many :users, through: :user_stocks
Ok, I get that the first argument is :users and the second is the same as
:through => :user_stocks
correct?
But in the same way, let's look at an routes.rb config from the same project:
resources :user_stocks, except: [:show, :edit, :update]
Now we're using an array of keys on the except hash, correct?
It does get clearer when writing an question but still, is there a rule of thumb / convention on when to use
:name
name: value
:name => {values}?
name: [values]
Or is it just an personal preference? In that case I should hope that my online teacher stays consistent..
Generally speaking, I'm very confused on how the parameter syntax convention is and when to use what (what type of argument).
Is it just because I am starting with Ruby or did I miss some piece of convention.
I hope my problem is kind of understandable and excuse my english - non native speaker.
I really like to get along with RoR but right now watching the online course sometimes leaves me more confused than before because if I would've done it by myself, I would've used a completely different way.
How the heck should I know how to declare those parameters?
You look up the method in the docs and you read about it.
Parameters parsed to this method are:
:session,
:html => {class: "form-horizontal", role: "form"},
url: login_path
How the heck should I know how to declare those parameters? Why are
:session and :html passend in as keys and url not? Is the :html symbol
a Hashmap-symbol?
In ruby, if you pass in a series of key-value pairs at the end of the argument list, ruby gathers them all into a hash and passes them as one argument to the method. Here is an example:
def go(x, y)
p x
p y
end
go(:hello, a: 10, b: 20)
--output:--
:hello
{:a=>10, :b=>20}
Another example:
def go(x, y)
p x
p y
end
go(
:session,
:html => {class: "form-horizontal", role: "form"},
url: 'xyz'
)
--output:--
:session
{:html=>{:class=>"form-horizontal", :role=>"form"}, :url=>"xyz"}
has_many :users, through: :user_stocks
Ok, I get that the first argument is :users and the second is the same
as
:through => :user_stocks
correct?
Correct. In old ruby, key-value pairs in hashes were written like this:
'a' => 'hello'
If the value was a symbol, then it looked like this:
'a' => :hello
If the key was also a symbol, then you wrote:
:a => :hello
In modern ruby, if the key is a symbol you can write:
a: 'hello'
which is a shortcut for:
:a => 'hello'
and if the value is a symbol as well, in modern ruby it looks like this:
a: :hello
which is a shortcut for:
:a => :hello
resources :user_stocks, except: [:show, :edit, :update]
Now we're using an array of keys on the except hash, correct?
The hash isn't named except, but otherwise you are correct.
a rule of thumb / convention on when to use
:name # Single symbol argument
name: value # A key-value pair in a hash. The key is a symbol.
:name => {values}? #A key-value pair in a hash. The value looks like a hash, but the syntax is incorrect.
name: [values] #A key-value pair in a hash. The value is your notation for an array.
Or is it just an personal preference? In that case I should hope that my online teacher stays consistent..
Once again, a method can be defined to take any type of argument. Because ruby variables don't have types, you have to check the docs. If a method expects you to pass in a hash where the key :name has a value that is a hash, then you need to do that. On the other hand, if the method expects you to pass in a hash where the key :name has a value that is an array, then you need to do that.
Generally speaking, I'm very confused on how the parameter syntax
convention is and when to use what (what type of argument). Is it just
because I am starting with Ruby or did I miss some piece of
convention.
Ruby has a lot of shortcuts, which can be confusing to a beginner. Then there is the whole String v. Symbol concept. If you can understand the practical difference between a Symbol and a String, then you will be ahead of the game. A Symbol is like an integer. So when ruby has to compare whether Symbols are equal, ruby compares two integers, which is fast. If ruby has to compare Strings, then ruby has to compare the ascii code of each letter in one string to the ascii code of each letter in the other string until ruby finds a difference. For instance, in order for ruby to compare the following two Strings:
"helloX" v. "helloY"
ruby won't find a difference until after it has made six integer comparisons:
'h' v 'h' => equal
'e' v 'e' => equal
...
...
'X' v 'Y' => not equal
On the other hand, if ruby were comparing:
:helloX v. :helloY
the Symbols are essentially stored as single integers, something like:
341343 v. 134142 => not equal
To compare them takes only a single integer comparison, so it's faster. As someone is sure to point out, that isn't quite how Symbols are implemented, but the details don't matter. It's sufficient to know that Symbol comparisons are faster than String comparisons, and as to why that is true the example above is sufficient to demonstrate there is at least one implementation where it can be true.
Hashes: hashrockets vs literal
In Ruby hashes can use either "hashrockets" or the newer literal notation (since Ruby 1.9):
# hashrockets
{ :foo => "bar" }
# literal
{ foo: "bar" }
They both do the exact same thing. They create a hash with the symbol :foo as a key. The literal syntax is now generally preferered.
Hashrockets should only be used today if you have something other than symbols as keys (you can have numbers, strings or any object as keys):
{ 1 => 'a', 2 => 'b' }
# using literals won't work here since it will cast the keys to symbols:
{ 1: 'a', 2: 'b' } # => { :1 => 'a', :2 => 'b' }
As Ruby is loosly typed hashes can contain any kinds of values:
{ foo: "bar" }
{ foo: [:bar, :baz] }
{ foo: { bar: :baz } }
Hash options
In Ruby methods can recieve both ordinal arguments and a hash with options:
def foo(bar, hash = {})
#test= hash[:]
end
The hash options must come after the positional arguments in the arguments list.
# syntax error
foo(a: 2, "test")
# good
foo("test", a: 2)
When passing a hash options you can forgo the surrounding brackets as they are implied:
foo("test", { a: 1, b: 2 })
# same result but nicer to read
foo("test", a: 1, b: 2 )
Ruby 2.0 introduced keyword arguments which reduces the amount of boilerplate code needed to parse the options:
def foo(bar, test: nil)
#test= test
end
def foo(bar, test: nil, **kwargs)
#test= test
# all the options except test
puts kwargs.inspect
end
# will raise an error if the test key is not passed
def foo(bar, test:)
#test= test
end
Postitional arguments vs hash options
Postitional arguments are shorter and ideal where the order of the parameters is self explainatory:
class Number
def add(x)
#val += x
end
end
When you have more complex methods with a large number of arguments it can be tricky to keep track of the order:
def book_flight(destination, seats, airline_preference= nil, special_meals= nil)
end
thats where hash options come in and shine as you can have an arbirtrary number of options and the program does not blow up because you forgot a nil in the call.

Rails - How to define setter/getter dynamically for list of methods inside a class

I have a Notifications module which have classes like 1)car 2)bike 3)Aeroplane. I have a serialized column in UserFeature model.And I have a module 'Notifications' which has list of 11 classes in it.
Notifications
1)car
2)bike
3)Aeroplane
The hash structure of the column notifications in UserFeature model must be
{:car => {:mirror => :true, :door => :true}
:bike => {:x=> :true, :x => :true}
:Aeroplane => {:p => :true, :q => :true}
}
I can access user_object.Notifications
But so as to access user_object.car and also user_object.mirror I need to write getter/setter methods { Defining getter/setter dynamically because I dont want to write getter/setter for every method and also I am unsure about the number of methods I have -> which in future may extend }
Notifications.constants.each do |notification_class|
class_methods = "Notifications::#{notification_class}".constantize.methods(false)
class_methods.each do |method|
method_name = method[0..-4].split('(')[0]
setter_getter_name = "#{notification_class.to_s.underscore}_#{method_name}"
define_method("#{setter_getter_name}=") do |value|
self.notifications = GlobalUtils.form_hash(self.notifications, "#{notification_class}".to_sym, "#{method_name}".to_sym)
self[:notifications]["#{notification_class}".to_sym][ "#{method_name}".to_sym] = value
end
define_method("#{setter_getter_name}") do
self.notifications.fetch("#{notification_class_name}".to_sym, {}).fetch("#{method_name}".to_sym)
end
end
end
But still when i try to access user_object.mirror,
undefined method for #<UserFeature000043645345>
What I am doing wrong?
I need to do this using getter/setter method only
An OpenStruct is a data structure, similar to a Hash, that allows the definition of arbitrary attributes with their accompanying values. This is accomplished by using Ruby’s metaprogramming to define methods on the class itself.
example:
require 'ostruct'
hash = { "country" => "Australia", :population => 20_000_000 }
data = OpenStruct.new(hash)
p data # -> <OpenStruct country="Australia" population=20000000>
Use Ruby OpenStruct class. It will fulfill your requirements without defining such bunch of code.
Edit1, example:
require 'ostruct'
class Aeroplane < OpenStruct; end
a = Aeroplane.new(:p => :true, :q => :true)
a.p # => true

Which to use :foo and foo:

What is the difference between these two?
render plain: params[:article].inspect
render :plain params[:article].inspect
For the second statement, a syntax error occurred. What's wrong with this?
Besides, I experimented
render plain: params[article:].inspect`.
Using params[article:] here will lead to a syntax error. Why do we have to use params[:article] instead of params[article:] here?
In Ruby hash syntax is key => value, where key can be any (almost) object, ex:
'my_key' => 1
:my_key => 2
'my_key' is a String. :my_key is a Symbol. The Symbol syntax is to put the colon before.
And the 'colon after symbol' syntax - symbol: object - is a syntactic sugar for :symbol => object. It means that this line:
:my_key => 2
is exactly the same as:
my_key: 2
You can read about it in my article Ruby for Admins: Hashes
The plain: usage is valid shortcut for symbols when you're declaring a literal hash starting on Ruby 1.9, as in:
some_method(class: MyClass, name: 'register-me')
It's also valid when declaring named parameters which is new to Ruby 2.1. You would do it like this:
def some_method( key: 'some key' )
end
And then call it like this:
some_method # assumes default value
some_method(key: "another key")
For all other cases, symbols are still declared as :some_name and you will have to declare them like this.
The reason your second sentence isn't working is because you are missing a =>:
render :plain => params[:article].inspect
So these two are equivalent:
foo: "bar"
:foo => "bar"

Ruby/Rails convert string to a class attribute

Suppose I have a class Article, such that:
class Article
attr_accessor :title, :author
def initialize(title, author)
#title = title
#author= author
end
end
Also, variable atrib is a String containing the name of an attribute. How could I turn this string into a variable to use as a getter?
a = Article.new
atrib='title'
puts a.eval(atrib) # <---- I want to do this
EXTENDED
Suppose I now have an Array of articles, and I want to sort them by title. Is there a way to do the compact version using & as in:
col = Article[0..10]
sorted_one = col.sort_by{|a| a.try('title') } #This works
sorted_two = col.sort_by(&:try('title')) #This does not work
You can use either send or instance_variable_get:
a = Article.new 'Asdf', 'Coco'
a.pubic_send(:title) # (Recommended) Tries to call a public method named 'title'. Can raise NoMethodError
=> "Asdf"
# If at rails like your case:
a.try :title # Tries to call 'title' method, returns `nil` if the receiver is `nil` or it does not respond to method 'title'
=> "Asdf"
a.send(:title) # Same, but will work even if the method is private/protected
=> "Asdf"
a.instance_variable_get :#title # Looks for an instance variable, returns nil if one doesn't exist
=> "Asdf"
Shot answer to your extended question: no. The &:symbol shortcut for procs relies on Symbol#to_proc method. So to enable that behavior you'd need to redifine that method on the Symbol class:
class Symbol
def to_proc
->(x) { x.instance_eval(self.to_s) }
end
end
[1,2,3].map(&:"to_s.to_i * 10")
=> [10, 20, 30]
ActiveRecord instances have an attributes hash:
a = Article.new(title: 'foo')
#=> <#Article id: nil, title: "foo">
atrib = 'title'
a.attributes[atrib]
#=> "foo"
You can use order to get sorted objects from your database:
Article.order('title').first(10)
#=> array of first 10 articles ordered by title

What does :attribute => parameter actually do?

I have a hard time understanding the form :attribute => parameter
Can anyone give me some explanations for it? Is :attribute a field (variable) belonging to the class or something else? Why we can pass this pair as one parameter to methods?
If you're referring to something like this:
some_method(:foo => "bar", :baz => "abc")
then it's just shorthand which causes ruby to convert those things into a Hash. Please note that when using this form, that the hash must be the final argument to the method in order for this to work.
Based on the explanation above, this
some_method(:foo => "bar", :baz => "abc")
is ok, but this
some_method(:foo => "bar", :baz => "abc", moo)
is not.
Though you will see this commonly in Rails, it is not a Rails specific question. It is Ruby.
The answer to your question is that it is key/value pairs in a Hash, generally passed as an argument to a method.
You will see this as well when it is being assigned to a variable directly. But let me show you a sample method, and a sample usage, so that you can put them together:
def some_method(*args, name: 'Joe', amount: 42, **other_params )
puts "#{name}, #{amount}, glob of arguments = #{args.inspect}",
"other params #{other_params}"
end
some_method(:occupation => 'programmer', :phone => '123-456-7890', name: 'Jane')
This is Ruby 2.0.0 specific in the fact that you can provide for that last argument, which provides for unnamed parameters, in practice. Using the 1.9+ syntax for a Hash in the argument list, you are allowed to provide for other unnamed "parameters" which can appear after the hash argument.
Notice that if I had used the older syntax for Hash, namely the :key => 'value' syntax, I would not be allowed (at least as of this writing) to have the **other_params argument at the end of the argument list.
You could also provide the hash using the newer syntax in the calling code, though I left it as the Hash syntax when calling some_method.
The Hash still needs to be the last provided in the calling argument list, the same as indicated in the argument list for the method definition.

Resources