For the ActionDispatch::Routing::Mapper::Base module, the match method is defined like this:
match(path, options=nil)
One thing I find challenging in the Rails documentation is that it doesn't tell me what the type of some of these parameters are. So let's look at some of the examples:
match ':controller/:action/:id'
Here, path is a string.
match 'songs/*category/:title' => 'songs#show'
Here, it's a hash. Or is it a string still? I'm not sure how to interpret this syntax. Is it:
{ match 'songs/*category/:title' => 'songs#show' }
where match 'songs/*category/:title' is the key and 'songs#show' is the value? Or:
match {'songs/*category/:title' => 'songs#show'}
where the match method is being called with a hash as the first argument?
It's either a string or a hash. And it's the second one:
match {'songs/*category/:title' => 'songs#show'}
But the hash is actually an argument, so more correctly it would be:
match({'songs/*category/:title' => 'songs#show'})
You can try that yourself with a mock method in the irb:
irb(main):005:0> def match(foo)
irb(main):006:1> puts foo
irb(main):007:1> end
=> nil
irb(main):008:0> match "somestring"
somestring
=> nil
irb(main):009:0> match :has => "hash"
{:has=>"hash"}
=> nil
It is a hash as in
match({'songs/*category/:title' => 'songs#show'})
At the final argument position, you can omit the {}. But you cannot write it like
match {'songs/*category/:title' => 'songs#show'}
because the {} will be interpreted as a block, and will cause a syntax error without (), which explicitly indicates that it is an argument.
For match ':controller/:action/:id', `path is a String.
For match 'songs/*category/:title' => 'songs#show', path is a Hash:
1.9.3p125 :001 > def match(path, options=nil)
1.9.3p125 :002?> puts path
1.9.3p125 :003?> puts options
1.9.3p125 :004?> end
=> nil
1.9.3p125 :005 > match 'foo' => 'bar'
{"foo"=>"bar"}
=> nil
Related
I'm using RoR 5.0.1. I have a model that is not persisted to a database. It has two fields
first_field
second_field
How do I force different instantiations of the object in which its fields have the same value to have the same hash value? Right now it seems each unique creation of an object has a different hash value, even if the individual attributes have the same values . So if I create two different objects
o1 = MyObject.new({:first_field => 5, :second_field => "a"})
o2 = MyObject.new({:first_field => 5, :second_field => "a"})
I'd like them to have the same hash values even though they are different instantiations of the object.
The behavior you're looking for isn't entirely clear from your question. However, if you want (as Michael Gorman asks) the instances of MyObject to share the same hash instance (such that changes to the values o1 are reflected in the values of o2), then you could do something like:
class MyObject
def initialize(hsh = {})
#hsh = hsh
hsh.each do |k,v|
class_eval do
# define the getter
define_method(k) do
#hsh[k]
end
# define the setter
define_method("#{k}=") do |val|
#hsh[k] = val
end
end
end
end
end
Then create a single instance of a hash:
hsh = {first_field: 5, second_field: "a"}
And instantiate your two objects with this hash:
o1 = MyObject.new(hsh)
o2 = MyObject.new(hsh)
Both instances will have the same first_field value:
2.3.1 :030 > o1.first_field
=> 5
2.3.1 :031 > o2.first_field
=> 5
And changes in o1.first_field will be reflected in o2.first_field:
2.3.1 :033 > o1.first_field = 7
=> 7
2.3.1 :034 > o1.first_field
=> 7
2.3.1 :035 > o2.first_field
=> 7
Same with second_field:
2.3.1 :037 > o1.second_field
=> "a"
2.3.1 :038 > o2.second_field
=> "a"
2.3.1 :040 > o1.second_field = "b"
=> "b"
2.3.1 :041 > o1.second_field
=> "b"
2.3.1 :042 > o2.second_field
=> "b"
Since the setter and getter are generated dynamically, you could do something like:
hsh = {first_field: 5, second_field: "a", nth_field: {foo: :bar}}
o1 = MyObject.new(hsh)
And o1 will respond to nth_field without having to do any extra coding on MyObject:
2.3.1 :048 > o1.nth_field
=> {:foo=>:bar}
I'm not sure if I'm even asking the right question. I may be approaching the problem incorrectly, but basically I have this situation here:
obj = get_user(params)
obj.profile => {:name => "John D", :age => 40, :sex => "male"} #Has to be of class Hash
obj.profile.name => "John D"
obj.profile[:name] => "John D"
obj.profile.job => nil
So basically, I have to satisfy all of these conditions and I'm not sure exactly how to even approach this (I just learned Ruby today).
Note the dot notation for accessing the inner variables, otherwise I would have just had profile be a hash of symbols. So I've tried two methods, which only sort of get me there
Method 1: Make profile an OpenStruct
So this allows me to access name, age and sex using the dot notation, and it automatically returns nil if a key doesn't exist, however obj.profile is of the type OpenStruct instead of Hash
Method 2: Make profile its own class
With this I set them as instance variables, and I can use method_missing to return nil if they don't exist. But, I again run into the issue of obj.profile not being the correct type/class
Is there something I'm missing? Is there a way to maybe differentiate between
obj.profile
obj.profile.name
in the getter function and return either a hash or otherwise?
Can I change what is returned by my custom class for profile, so it returns a Hash instead?
I've even tried checking the args and **kwargs in the get function for obj.profile and neither of them seem to help, or populate if I call obj.profile.something
If it absolutely has to be a Hash:
require 'pp'
module JSHash
refine Hash do
def method_missing(name, *args, &block)
if !args.empty? || block
super(name, *args, &block)
else
self[name]
end
end
end
end
using JSHash
profile = {:name => "John D", :age => 40, :sex => "male"}
pp profile.name # "John D"
pp profile[:name] # "John D"
pp profile.job # nil
pp profile.class # Hash
But still better not to be a Hash, unless it absolutely needs to:
require 'pp'
class Profile < Hash
def initialize(hash)
self.merge!(hash)
end
def method_missing(name, *args, &block)
if !args.empty? || block
super(name, *args, &block)
else
self[name]
end
end
end
profile = Profile.new({:name => "John D", :age => 40, :sex => "male"})
pp profile.name
pp profile[:name]
pp profile.job
For only a few hash keys, you can easily define singleton methods like so:
def define_getters(hash)
hash.instance_eval do
def name
get_val(__method__)
end
def job
get_val(__method__)
end
def get_val(key)
self[key.to_sym]
end
end
end
profile = person.profile #=> {name: "John Doe", age: 40, gender: "M"}
define_getters(profile)
person.profile.name #=> "John Doe"
person.profile.job #=> nil
Reflects changed values as well (in case you were wondering):
person.profile[:name] = "Ralph Lauren"
person.profile.name #=> "Ralph Lauren"
With this approach, you won't have to override method_missing, create new classes inheriting from Hash, or monkey-patch the Hash class.
However, to be able to access unknown keys through method-calls and return nil instead of errors, you'll have to involve method_missing.
This Hash override will accomplish what you're trying to do. All you need to do is include it with one of your class files that you're already loading.
class Hash
def method_missing(*args)
if args.size == 1
self[args[0].to_sym]
else
self[args[0][0..-2].to_sym] = args[1] # last char is chopped because the equal sign is included in the string, print out args[0] to see for yourself
end
end
end
See the following IRB output to confirm:
1.9.3-p194 :001 > test_hash = {test: "testing"}
=> {:test=>"testing"}
1.9.3-p194 :002 > test_hash.test
=> "testing"
1.9.3-p194 :003 > test_hash[:test]
=> "testing"
1.9.3-p194 :004 > test_hash.should_return_nil
=> nil
1.9.3-p194 :005 > test_hash.test = "hello"
=> "hello"
1.9.3-p194 :006 > test_hash[:test]
=> "hello"
1.9.3-p194 :007 > test_hash[:test] = "success"
=> "success"
1.9.3-p194 :008 > test_hash.test
=> "success"
1.9.3-p194 :009 > test_hash.some_new_key = "some value"
=> "some value"
1.9.3-p194 :011 > test_hash[:some_new_key]
=> "some value"
I have a controller action where i am assigning a hash to an instance variable. In my rspec test file, i am using assigns to test it the instance variable is assigned to the value i expect. For some reason, assigns gives me a hash with string keys. If i print the instance variable in the controller, i has symbol keys
Please find the code below. It is simplified.
class TestController < ApplicationController
def test
#test_object = {:id => 1, :value => 2, :name => "name"}
end
end
My test file:
describe TestController do
it "should assign test_object" do
get :test
assigns(:test_object).should == {:id => 1, :value => 2, :name => "name"}
end
end
The above test fails with the error message
expected: {:id=>1, :value=>2, :name=>"name"}
got: {"id"=>1, "value"=>2, "name"=>"name"}
Please help me understand why it is doing that.
RSpec borrows assigns from the regular Rails test/unit helpers and it's using with_indifferent_access to return the requested instance variable as in assigns(:my_var).
Hash#with_indifferent_access returns a key-stringified version of the hash (a deep copy), which has the side effect of stringfiying the keys of instance variables that are hashes.
If you try to match the entire hash, it will fail, but it works if you are checking the values of specific keys, whether they're a symbol or a string.
Maybe an example will help clarify:
{:a => {:b => "bravo"}}.with_indifferent_access => {"a"=>{"b"=>"bravo"}}
{:a => {:b => "bravo"}}.with_indifferent_access[:a][:b] => "bravo"
I am using Ruby on Rails 3.1.0 and I would like to check if an hash is "completely" included in another hash and return a boolean value.
Say I have those hashes:
hash1 = {
:key1 => 'value1',
:key2 => 'value2',
:key3 => 'value3'
}
hash2 = {
:key1 => 'value1',
:key2 => 'value2',
:key3 => 'value3',
:key4 => 'value4',
:key5 => 'value5',
...
}
I would like to check if the hash1 is included in the hash2 even if in the hash2 there are more values than hash1 (in the above case the response that I am looking for should be true)? Is it possible to do that by using "one only code line"\"a Ruby method"?
That will be enough
(hash1.to_a - hash2.to_a).empty?
The easiest way I can think of would be:
hash2.values_at(*hash1.keys) == hash1.values
the more elegant way is to check the equality when one hash merge another.
e.g. rewrite Hash include? instance method for this.
class Hash
def include?(other)
self.merge(other) == self
end
end
{:a => 1, :b => 2, :c => 3}.include? :a => 1, :b => 2 # => true
There is a way:
hash_2 >= hash_1
Alternatively:
hash_1 <= hash_2
More info in this post: https://olivierlacan.com/posts/proposal-for-a-better-ruby-hash-include/
The most efficient and elegant solution I've found - with no intermediary arrays, or redundant loops.
class Hash
alias_method :include_key?, :include?
def include?(other)
return include_key?(other) unless other.is_a?(Hash)
other.all? do |key, value|
self[key] == value
end
end
end
Since Ruby 2.3 you can use built-in Hash#<= method.
Since Ruby 2.3, you can do this:
hash1 <= hash2
I am not sure if I understand the inclusion idea in hash.
To see if it has the same keys(usual problem).
All keys in hash1 are included in hash2:
hash1.keys - hash2.keys == []
Then if you want to compare those values do as suggested in the previous post:
hash1.values - hash2.values_at(*hash1.keys) == []
If I already have a hash, can I make it so that
h[:foo]
h['foo']
are the same? (is this called indifferent access?)
The details: I loaded this hash using the following in initializers but probably shouldn't make a difference:
SETTINGS = YAML.load_file("#{RAILS_ROOT}/config/settings.yml")
You can just use with_indifferent_access.
SETTINGS = YAML.load_file("#{RAILS_ROOT}/config/settings.yml").with_indifferent_access
If you have a hash already, you can do:
HashWithIndifferentAccess.new({'a' => 12})[:a]
You can also write the YAML file that way:
--- !map:HashWithIndifferentAccess
one: 1
two: 2
after that:
SETTINGS = YAML.load_file("path/to/yaml_file")
SETTINGS[:one] # => 1
SETTINGS['one'] # => 1
Use HashWithIndifferentAccess instead of normal Hash.
For completeness, write:
SETTINGS = HashWithIndifferentAccess.new(YAML.load_file("#{RAILS_ROOT}/config/settings.yml"))
You can just make a new hash of HashWithIndifferentAccess type from your hash.
hash = { "one" => 1, "two" => 2, "three" => 3 }
=> {"one"=>1, "two"=>2, "three"=>3}
hash[:one]
=> nil
hash['one']
=> 1
make Hash obj to obj of HashWithIndifferentAccess Class.
hash = HashWithIndifferentAccess.new(hash)
hash[:one]
=> 1
hash['one']
=> 1