I want to define a class method that has access to a local variable. So this would be different for each instance of the class. I know you can make a class method dynamic with lambda like when you use it with named_scope. But can this be done for values that are specific to an instance?
In detail it is the has_attached_file method for the paperclip plugin in rails. I want to pass a lambda for the styles hash so that the image styles can be based off of attributes of the object stored in the DB. Is this possible?
Disclaimer: First, the question (Can you pass self to lambda?) and the problem you're trying to solve (dynamic styles with paperclip) don't fully match up. I won't answer the original question because it's not entirely related to your problem, and rampion took a valiant stab at it.
I'll instead answer your paperclip question.
In detail it is the has_attached_file method for the paperclip plugin in rails. I want to pass a lambda for the styles hash so that the image styles can be based off of attributes of the object stored in the DB. Is this possible?
Yes, it is possible. In paperclip, the :styles option can take a Proc. When the attachment is initialized, if a Proc was used, the attachment itself is passed to the Proc. The attachment has a reference to the associated ActiveRecord object, so you can use that to determine your dynamic styles.
For example, your has_attached_file declaration might look something like this (assuming a User and avatar scenario where the user can customize the size of their avatar):
class User < ActiveRecord::Base
has_attached_file :avatar, :styles => lambda { |attachment|
user = attachment.instance
dimensions = "#{user.avatar_width}x#{user.avatar_height}#"
{ :custom => dimensions }
}
end
Ok, you're being unclear.
Local variables in ruby begin with a lowercase letter (like foo, bar, or steve), and are lexically scoped (like C variables). They have nothing to do with "an instance of a class"
Instance variables in ruby begin with an # sigil (like #foo, #bar, or #carl), and are in scope whenever the current value of self is the object they are stored in.
If you want a method that can access the instance variables of an object directly, that's called an instance method. For example, battle_cry and initialize are both instance methods:
class Character
def initialize(name)
#name=name
end
def battle_cry
#name.upcase + "!!!"
end
def Character.default
new("Leeroy Jenkins")
end
end
A class method, by contrast, is a method for a Class object, and doesn't have access to any of the instance variables of that object. In the above example,
default is a class method.
If you want a (class or instance) method that triggers a change in or gets a value from the current scope, ruby uses a type of callback called a block.
class Character
ATTACKS = [ "Ho!", "Haha!", "Guard!", "Turn!", "Parry!", "Dodge!", "Spin!", "Ha", "THRUST!" ]
def attack
ATTACKS.inject(0) { |dmg, word| dmg + yield(word) }
end
end
person = Character.default
puts person.battle_cry
num_attacks = 0;
damage = person.attack do |saying|
puts saying
num_attacks += 1
rand(3)
end
puts "#{damage} points of damage done in #{num_attacks} attacks"
In the above example, attack uses the yield keyword to call the block passed
to it. When we call attack, then, the local variable num_attacks is still
in scope in the block we pass it (delimited here by do ... end), so we can
increment it. attack is able to pass values into the block, here
they are captured into the saying variable. The block also passes values
back to the method, which show up as the return value of yield.
The word lambda in ruby usually means the lambda keyword, which is used
to make blocks into freestanding, function like objects (which themselves are usually
referred to as lambdas, procs, or Procs).
bounce = lambda { |thing| puts "I'm bouncing a #{thing}" }
bounce["ball"]
bounce["frog"]
So I think what you're asking is whether you can pass a Proc in place of a Hash
for an argument to a method. And the answer is "it depends". If the method only
ever uses the #[] method, then yes:
class Character
attr_accessor :stats
def set_stats(stats)
#stats = stats
end
end
frank = Character.new("Victor Frankenstein")
frank.set_stats({ :str => 7, :dex => 14, :con => 9, :int => 19, :wis => 7, :cha => 11 })
monster = Character.new("Frankenstein's Monster")
monster.set_stats(lambda do |stat_name|
rand(20)
end)
However, it might use some other Hash specific methods, or call the same key multiple times,
which can produce weird results:
monster = Character.new("Frankenstein's Monster")
monster.set_stats(lambda do |stat_name|
rand(20)
end)
monster.stats[:dex] #=> 19
monster.stats[:dex] #=> 1
In which case, you may be better off caching the requests in an intermediate hash. This is fairly easy,
since a Hash can have an initializer block. So if we change the above to:
monster.set_stats(Hash.new do |stats_hash, stat_name|
stats_hash[stat_name] = rand(20)
end)
monster.stats[:dex] #=> 3
monster.stats[:dex] #=> 3
The results are cached in the hash
To see more about Hash block initializers, see ri Hash::new:
-------------------------------------------------------------- Hash::new
Hash.new => hash
Hash.new(obj) => aHash
Hash.new {|hash, key| block } => aHash
------------------------------------------------------------------------
Returns a new, empty hash. If this hash is subsequently accessed
by a key that doesn't correspond to a hash entry, the value
returned depends on the style of new used to create the hash. In
the first form, the access returns nil. If obj is specified, this
single object will be used for all default values. If a block is
specified, it will be called with the hash object and the key, and
should return the default value. It is the block's responsibility
to store the value in the hash if required.
h = Hash.new("Go Fish")
h["a"] = 100
h["b"] = 200
h["a"] #=> 100
h["c"] #=> "Go Fish"
# The following alters the single default object
h["c"].upcase! #=> "GO FISH"
h["d"] #=> "GO FISH"
h.keys #=> ["a", "b"]
# While this creates a new default object each time
h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" }
h["c"] #=> "Go Fish: c"
h["c"].upcase! #=> "GO FISH: C"
h["d"] #=> "Go Fish: d"
h.keys #=> ["c", "d"]
Related
i'm trying (and actually succeded, but i don't understand how it works) to write a custom method for a hash in my model (I'm working on Ruby on Rails 6).
My hash looks like this
my_hash = {
[['name_1', 'slug_1']=>value_1],
[['name_2', 'slug_2']=>value_2],
[['name_1', 'slug_1']=>value_3],
[['name_2', 'slug_2']=>value_4]
}
So basically a hash of arrays. You notice that the 'keys' are arrays that repeat themselves many times, but with different values. What i want to achieve is to write a custom method that "joins" all the keys in only one key, which will have an array of values assigned, so basically i should be able to get:
my_hash = {
['name_1', 'slug_1']=>"values": [value_1, value_3],
['name_2', 'slug_2']=>"values": [value_2, value_4]
}
For that, I have this piece of code, which i use many times:
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
Since I use this many times, i wanted to write a custom method, so i did:
def format_hash_data my_hash
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end
And used it like: my_hash = format_hash_data(my_hash) with no success(it threw an error saying that 'format_hash_data' was not a valid method for the class).
So I fiddled around and added 'self' to the name of the method, leaving:
def self.format_hash_data my_hash
my_hash.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end
Which, to my surprise, worked flawlessly when using my_hash = format_hash_data(my_hash)
I don't really understand why adding 'self' makes my code works, maybe anyone can shed some light? I tried using things like send() or instance_eval first, to just send the piece of code to the actual hash as a method (something like my_hash.instance_eval(my_method)) but I couldn't get it working.
I'm sorry about the long explanation, I hope i was clear enough so any of you who had this same dilemma can understand. Thanks in advance.
Prepending self. to the method name makes it a class method instead of an instance method. If you are not sure of the difference, you should look it up as it is fundamental to properly defining and using classes and methods.
As a class method, you would use it as:
my_hash = MyHash.format_hash_data(my_hash)
Or if you're in scope of the class, simply my_hash = format_hash_data(my_hash), which is why it worked in your case with the self. prepended (class method definition).
If you want to define it as an instance method (a method that is defined for the instance), you would use it like so:
my_hash = my_hash.format_hash_data
And the definition would use the implicit self of the instance:
def format_hash_data
self.inject({}) do |hash, record|
# each record has the following format => [["unit_name", "axis.slug"]=>average_value(float)]
keys, value = record
# now keys has ["unit_name", "axis.slug"] and values equals average_value
hash[keys.first] ||= {}
hash[keys.first][keys.last] = value.to_f
hash
end
end
Having a hashmap, such as:
{:foo => 1, :bar => 2}
in Ruby, is there an easy way to assign those values as properties of the object, to automatically cause this to happen:
obj.foo = 1
obj.bar = 2
To be precise, some Ruby-idiomatic way of doing:
hashmap.each { |k,v| obj.send("#{k}=", v) }
obj is an object that doesn't inherit ActiveModel and it's not a Struct and I can't control it's type as it's coming from a third party library.
I'm using Rails, so if the answer comes from Rails, that's acceptable.
What you have there is (almost) the most concise, readable, idiomatic solution already:
hashmap.each { |k,v| obj.send("#{k}=", v) }
There is only one thing left to improve:
hashmap.each { |k,v| obj.public_send("#{k}=", v) }
Use public_send instead of send to make it clear to others that you are using it only to pass a method name dynamically and not to circumvent access restrictions.
Maybe you could create an OpenStruct from your hash, do whatever you need with the OpenStruct and its attributes, then convert it back into a hash?
require 'ostruct'
h = {:foo => 1, :bar => 2}
o = OpenStruct.new(h)
o.foo # Output: => 1
o.bar # Output: => 2
# if necessary, convert it back into a hash
h = o.to_h
From the Ruby docs:
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.
An OpenStruct utilizes Ruby’s method lookup structure to and find and
define the necessary methods for properties. This is accomplished
through the method method_missing and define_method.
This should be a consideration if there is a concern about the
performance of the objects that are created, as there is much more
overhead in the setting of these properties compared to using a Hash
or a Struct.
Doing this on mobile so let's give this a try:
class Something
attr_reader :foo, :bar
def initialize(hash = {})
foo = hash[:foo]
bar = hash[:bar]
end
end
obj = Something.new({foo: 1, bar: 2})
obj.foo = 1
obj.bar =2
class Klass
def initialize(h)
h.each { |iv, val| instance_variable_set("##{iv}", val) }
end
end
k = Klass.new(:foo => 1, :bar => 2)
#=> #<Klass:0x007ff1d9073118 #foo=1, #bar=2>
You can use method_missing if you need to set data dynamically.
class Foo
def method_missing(sym, *args)
super unless instance_variables.include?("##{sym}".to_sym)
instance_variable_get("##{sym}")
end
end
obj = Foo.new
h = {:foo => 1, :bar => 2}
h.each { |k, v| obj.instance_variable_set("##{k}", v) }
obj.foo
# => 1
obj.bar
# => 2
If you are using Rails, a minor improvement on your suggested solution would be to use Object#try from Active support extensions
hashmap.each {|k,v| obj.try "#{k}=", v }
As per documentation,
Invokes the public method whose name goes as first argument just like
public_send does, except that if the receiver does not respond to it
the call returns nil rather than raising an exception.
Here's my Rails class
class SkinnyEmployee
include ActiveModel::Validations
attr_accessor :uid, :name
validates :uid, :presence => true
def initialize(id, name)
#uid = id
#name = name
end
def ==(other)
puts "Calling =="
raise ArgumentError.new("other is nil or bad in "+self.to_s) if other.nil? or !other.instance_of?(SkinnyEmployee)
return (self.class == other.class && self.uid == other.uid)
end
alias :eql? :==
end
I have a hash of SkinnyEmployee objects. E.g.,
skinny_hash = {SkinnyEmployee.new("123", "xyz") => 1, SkinnyEmployee.new("456", "abc") => 2}
I have another SkinnyEmployee object that I want to look up. E.g.,
entry = SkinnyEmployee.new("456", "abc")
When I do
skinny_hash.keys.index(entry)
I get 1, as expected. But when I do
skinny_hash.has_key?(entry)
I get false.
Why is that? Doesn't has_key? also use == or eql? to find whether a key exists in a hash?
Thanks much for the help!
First, this drove me nuts. What you're doing looked absolutely correct to me, and, as you already know, doesn't work.
I can take you part of the way to a solution:
http://ruby-doc.org/core-2.0.0/Object.html#method-i-hash
quoting:
Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.
The hash value is used along with eql? by the Hash class to determine if two objects reference the same hash key. Any hash value that exceeds the capacity of a Fixnum will be truncated before being used.
I added:
def hash
1
end
to your SkinnyEmployee Class, and has_key? started returning true. Obviously that's not a solution, but I'm thinking it at least puts you on the path to one.
You have overwritten the eql? method used by Array#index but not the hash method used by Hash#has_key?.
From Ruby docs for Object#hash
Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.
The Object#hash and Object#eql? methods return equal if and only if the objects occupy the same space in memory. Some classes like Array overwrite both methods to return true if the compared array's have same elements.
For your case you can define the hash method like:
def hash
"#{self.class}_#{self.uid}".hash
end
This would satisfy the docs criteria for hash method given above.
That is happening because the object you are using as a key and they one you are using to search the key are different.
Every time you call SkinnyEmployee.new it will create a new, different, object. For example
employee_1 = SkinnyEmployee.new("123", "xyz")
employee_2 = SkinnyEmployee.new("123", "xyz")
employee_1 == employee_1 #=> true
employee_2 == employee_2 #=> true
employee_2 == employee_1 #=> false
If you call object_id on both employee_1 and employee_2 you will notice that it gives you different id's.
Using has_key? will check for the exact same object, and that won't be the case if you use SkinnyEmployee.new("456", "abc").
You would need a way to retrieve the exact same object, store it in a variable or in the DB, you are using as a key and use it as an attribute for has_key? for it to work.
Hope this can help you.
This question already has answers here:
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
In Rails we can do the following in case a value doesn't exist to avoid an error:
#myvar = #comment.try(:body)
What is the equivalent when I'm digging deep into a hash and don't want to get an error?
#myvar = session[:comments][#comment.id]["temp_value"]
# [:comments] may or may not exist here
In the above case, session[:comments]try[#comment.id] doesn't work. What would?
You forgot to put a . before the try:
#myvar = session[:comments].try(:[], #comment.id)
since [] is the name of the method when you do [#comment.id].
The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.
A safe navigation operator, which already exists in C#, Groovy, and
Swift, is introduced to ease nil handling as obj&.foo. Array#dig and
Hash#dig are also added.
This means as of 2.3 below code
account.try(:owner).try(:address)
can be rewritten to
account&.owner&.address
However, one should be careful that & is not a drop in replacement of #try. Take a look at this example:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
It is also including a similar sort of way: Array#dig and Hash#dig. So now this
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
can be rewritten to
city = params.dig(:country, :state, :city)
Again, #dig is not replicating #try's behaviour. So be careful with returning values. If params[:country] returns, for example, an Integer, TypeError: Integer does not have #dig method will be raised.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try() calls, if you want the code still look nice:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
You should be careful with various objects (especially params), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash) is used instead of (usually better) .respond_to?(:[]):
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
end
end
a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}
puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".
The proper use of try with a hash is #sesion.try(:[], :comments).
#session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Update: As of Ruby 2.3 use #dig
Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).
The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].
It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):
class Hash
def get_deep(*fields)
fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
end
end
It can be used the same as Arsen7's solution but also supports arrays e.g.
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-
you can try:
params[:user].try(:[], :email)
It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.
As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
Or in the example above:
session.dig(:comments, #comment.id, "temp_value")
This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.
#myvar = session.fetch(:comments, {}).fetch(#comment.id, {})["temp_value"]
From Ruby 2.0, you can do:
#myvar = session[:comments].to_h[#comment.id].to_h["temp_value"]
From Ruby 2.3, you can do:
#myvar = session.dig(:comments, #comment.id, "temp_value")
Another approach:
#myvar = session[:comments][#comment.id]["temp_value"] rescue nil
This might also be consider a bit dangerous because it can hide too much, personally I like it.
If you want more control, you may consider something like:
def handle # just an example name, use what speaks to you
raise $! unless $!.kind_of? NoMethodError # Do whatever checks or
# reporting you want
end
# then you may use
#myvar = session[:comments][#comment.id]["temp_value"] rescue handle
When you do this:
myhash[:one][:two][:three]
You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:
Add the method:
#in lib/ruby_extensions.rb
class NilClass
def [](*args)
nil
end
end
Require the file:
#in config/initializers/app_environment.rb
require 'ruby_extensions'
Now you can call nested hashes without fear: i'm demonstrating in the console here:
>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
Andrew's answer didn't work for me when I tried this again recently. Maybe something has changed?
#myvar = session[:comments].try('[]', #comment.id)
The '[]' is in quotes instead of a symbol :[]
Try to use
#myvar = session[:comments][#comment.id]["temp_value"] if session[:comments]
I know these are the basics of rails but i still don't know the full difference between = sign and => and the difference between #some_variable, ##some_variable and :some_variable in rails.
Thanks.
OK.
The difference between the = and the => operators is that the first is assignment, the second represents an association in a hash (associative array). So { :key => 'val' } is saying "create an associative array, with :key being the key, and 'val' being the value". If you want to sound like a Rubyist, we call this the "hashrocket". (Believe it or not, this isn't the most strange operator in Ruby; we also have the <=>, or "spaceship operator".)
You may be confused because there is a bit of a shortcut you can use in methods, if the last parameter is a hash, you can omit the squiggly brackets ({}). so calling render :partial => 'foo' is basically calling the render method, passing in a hash with a single key/value pair. Because of this, you often see a hash as the last parameter to sort of have a poor man's optional parameters (you see something similar done in JavaScript too).
In Ruby, any normal word is a local variable. So foo inside a method is a variable scoped to the method level. Prefixing a variable with # means scope the variable to the instance. So #foo in a method is an instance level.
## means a class variable, meaning that ## variables are in scope of the class, and all instances.
: means symbol. A symbol in Ruby is a special kind of string that implies that it will be used as a key. If you are coming from C#/Java, they are similar in use to the key part of an enum. There are some other differences too, but basically any time you are going to treat a string as any sort of key, you use a symbol instead.
Wow, a that's a lot of different concepts together.
1) = is plain old assignment.
a = 4;
puts a
2) => is used to declare hashes
hash = {'a' => 1, 'b' => 2, 'c' => 3}
puts hash['b'] # prints 2
3) #var lets you access object instance variable.
class MyObject
def set_x(x)
#x = x
end
def get_x
#x
end
end
o = MyObject.new
o.set_x 3
puts o.get_x # prints 3
4) ##var lets you access class ('static') variables.
class MyObject
def set_x(x)
##x = x # now you can access '##x' from other MyObject instance
end
def get_x
##x
end
end
o1 = MyObject.new
o1.set_x 3
o2 = MyObject.new
puts o2.get_x # prints 3, even though 'set_x' was invoked on different object
5) I usually think of :var as special 'label' class. Example 2 can be rephrased like this
hash = {:a => 1, :b => 2, :c => 3}
puts hash[:b] # prints 2