Ruby: Get the value if end_with? is true - ruby-on-rails

Looking for a clean way to return the characters of a string if end_with? evaluates to true.
i.e.
s = "my_name"
name = s.end_with?("name")
puts name
>> "name"
My use case would look somewhat like this:
file_name = "some_pdf"
permitted_file_types = %w(image pdf)
file_type = file_name.end_with?(*permitted_file_types)
puts file_type
>> "pdf"

I would do this:
"my_name".scan(/name\z/)[0]
#=> "name"
"something".scan(/name\z/)[0]
#=> nil

May be using match and \z (end of string)?
string = "my_name"
suffix1 = "name"
suffix2 = "name2"
In Ruby >= 3.1
result1 = %r{#{suffix1}\z}.match(string)&.match(0)
# => "name"
result2 = %r{#{suffix2}\z}.match(string)&.match(0)
# => nil
In Ruby < 3.1
result1 = %r{#{suffix1}\z}.match(string)&.[](0)
# => "name"
result2 = %r{#{suffix2}\z}.match(string)&.[](0)
# => nil
Just for fun trick with tap:
string.tap { |s| break s.end_with?(suffix1) ? suffix1 : nil }
# => "name"
string.tap { |s| break s.end_with?(suffix2) ? suffix2 : nil }
# => nil

ruby already has a String#end_with? method, going back to at least version 2.7.1, maybe earlier. But it returns a boolean and you want the matched string to be returned. You're calling a method on an instance of String, so you're apparently wanting to add a method to the String class.
class String #yeah we're monkey patching!
def ends_with?(str) #don't use end_with.. it's already defined
return str if self.end_with?(str)
end
end
#now
s="my_name"
s.ends_with?("name") #=> "name"
But I really wouldn't bother with all that... I'd just work with what Ruby provides:
s = "my_name"
str = "name"
result = str if s.end_with?(str)

Related

Return members of a hashmap through a class get method

The following returns the default "client?":
class ClientMap
def initialize
##clients = {"DP000459": "BP"}
##clients.default = "client?"
end
def get(id)
return ##clients[:id]
end
end
clientMap = ClientMap.new
cKey = "DP000459"
puts clientMap.get(cKey)
Could anybody explain why I cannot retrieve anything but the 'default'?
You've got two problems. First, you are using the symbol syntax in your hash, which works only if your keys are symbols. If you want keys to be strings, you need to use hash-rocket syntax: ##clients = {'DP000459' => 'BP'}.
Second, your method returns clients[:id] regardless of what parameter is provided. The key is the symbol :id rather than the local variable id. You need to change this to ##clients[id].
Here's a cleaned-up version of what you want:
class ClientMap
def initialize
##clients = {'DP000459' => 'BP'}
##clients.default = 'client?'
end
def get(id)
##clients[id]
end
end
I've also taken the liberty of making the spacing more Ruby-idiomatic.
Finally, for variable names in Ruby, use snake_case:
>> client_map = ClientMap.new
>> c_key = 'DP000459'
>> client_map.get(c_key)
#> "BP"
Look at these code:
h = { foo: 'bar' } # => {:foo=>"bar"}
h.default = 'some default value' # => "some default value"
h[:foo] # => "bar"
h[:non_existing_key] # => "some default value"
You can read here about Hash#default method
Returns the default value, the value that would be returned by hsh if
key did not exist in hsh

Why to_json returned a String not equal with the same json String

In Rails console
> h_json = {key: "value"}.to_json;
#=> "{\"key\":\"value\"}"
> s_json = %Q|{"key": "value"}|
#=> "{\"key\": \"value\"}"
> s_json.class
#=> String
> h_json.class
#=> String
We can see both h_json and s_json have the same String class, and looks the same, however
#=> "{\"key\": \"value\"}"
> s_json == h_json
#=> false
They don't equals each other, I don't understand why.
there is a space in the s_json, if you checked the source code of the to_json function
# File activesupport/lib/active_support/json/encoders/hash.rb, line 33
def to_json(options = nil) #:nodoc:
hash = as_json(options)
result = '{'
result << hash.map do |key, value|
"#{ActiveSupport::JSON.encode(key.to_s)}:#{ActiveSupport::JSON.encode(value, options)}"
end * ','
result << '}'
end
this function doesn't add a space between the colon : and the value.
So actually,
h_json = "{\"key\":\"value\"}"
and
s_json = "{\"key\": \"value\"}"
if you set s_json = "{\"key\":\"value\"}" they must be equal.

Is there a method to set a value in rails to nil and save?

What I'm thinking of is something where I can say:
e = Foo.new
e.bar = "hello"
e.save
e.reload
e.bar.nil!
e.reload
e.bar.nil? => true
Kind of #touch but sets nil and saves.
EDIT
Super sorry guys. I mean this:
e = Foo.new
e.bar = "hello"
e.save
e.reload
e.bar.nil!
e.reload
e.bar.nil? => true
Maybe something like:
module ActiveRecord
class Base
def nil!(*names)
unless persisted?
raise ActiveRecordError, <<-MSG.squish
cannot nil on a new or destroyed record object. Consider using
persisted?, new_record?, or destroyed? before nilling
MSG
end
unless names.empty?
changes = {}
names.each do |column|
column = column.to_s
changes[column] = write_attribute(column, nil)
end
primary_key = self.class.primary_key
scope = self.class.unscoped.where(primary_key => _read_attribute(primary_key))
if locking_enabled?
locking_column = self.class.locking_column
scope = scope.where(locking_column => _read_attribute(locking_column))
changes[locking_column] = increment_lock
end
clear_attribute_changes(changes.keys)
result = scope.update_all(changes) == 1
if !result && locking_enabled?
raise ActiveRecord::StaleObjectError.new(self, "nil")
end
#_trigger_update_callback = result
result
else
true
end
end
end
end
Put that in an initializer and it'll let you null out the title of a comment with Comment.last.nil!(:title).
You can't save a nil to the database, and furthermore, once an object has been created as a particular class you can never change that. It can only be converted by creating a new object, something an in-place modifier like this hypothetical nil! does.
The closest thing you can get is:
e = Foo.new
e.bar = "hello"
e.save
e.reload
e.delete!
e.reload
e.destroyed? # => true
f = Foo.find_by(id: e.id)
f.nil? # => true

How to determine if a string is a number?

What is the easiest way to find out in Rails 3 whether a string str contains a positive float number or not ? (str is not an attribute in an active-record model)
It should work like this:
str = "123" => true
str = "123.456" => true
str = "0" => true
str = "" => false
str = "abcd" => false
str = "-123" => false
Here's one idea:
class String
def nonnegative_float?
Float(self) >= 0
rescue ArgumentError
return false
end
end
However, since you already seem to have a pretty good idea of what a nonnegative float number looks like, you could also match it against a Regexp:
class String
def nonnegative_float?
!!match(/\A\+?\d+(?:\.\d+)?\Z/)
end
end
You can match it against a regular expression.
str === /^\d+(\.\d+)?$/

what is the best way to convert a json formatted key value pair to ruby hash with symbol as key?

I am wondering what is the best way to convert a json formatted key value pair to ruby hash with symbol as key:
example:
{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }
==>
{ :user=>{ :name => 'foo', :age =>'40', :location=>{ :city => 'bar', :state=>'ca' } } }
Is there a helper method can do this?
using the json gem when parsing the json string you can pass in the symbolize_names option. See here: http://flori.github.com/json/doc/index.html (look under parse)
eg:
>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"}
Leventix, thank you for your answer.
The Marshal.load(Marshal.dump(h)) method probably has the most integrity of the various methods because it preserves the original key types recursively.
This is important in case you have a nested hash with a mix of string and symbol keys and you want to preserve that mix upon decode (for instance, this could happen if your hash contains your own custom objects in addition to highly complex/nested third-party objects whose keys you cannot manipulate/convert for whatever reason, like a project time constraint).
E.g.:
h = {
:youtube => {
:search => 'daffy', # nested symbol key
'history' => ['goofy', 'mickey'] # nested string key
}
}
Method 1: JSON.parse - symbolizes all keys recursively => Does not preserve original mix
JSON.parse( h.to_json, {:symbolize_names => true} )
=> { :youtube => { :search=> "daffy", :history => ["goofy", "mickey"] } }
Method 2: ActiveSupport::JSON.decode - symbolizes top-level keys only => Does not preserve original mix
ActiveSupport::JSON.decode( ActiveSupport::JSON.encode(h) ).symbolize_keys
=> { :youtube => { "search" => "daffy", "history" => ["goofy", "mickey"] } }
Method 3: Marshal.load - preserves original string/symbol mix in the nested keys. PERFECT!
Marshal.load( Marshal.dump(h) )
=> { :youtube => { :search => "daffy", "history" => ["goofy", "mickey"] } }
Unless there is a drawback that I'm unaware of, I'd think Method 3 is the way to go.
Cheers
There isn't anything built in to do the trick, but it's not too hard to write the code to do it using the JSON gem. There is a symbolize_keys method built into Rails if you're using that, but that doesn't symbolize keys recursively like you need.
require 'json'
def json_to_sym_hash(json)
json.gsub!('\'', '"')
parsed = JSON.parse(json)
symbolize_keys(parsed)
end
def symbolize_keys(hash)
hash.inject({}){|new_hash, key_value|
key, value = key_value
value = symbolize_keys(value) if value.is_a?(Hash)
new_hash[key.to_sym] = value
new_hash
}
end
As Leventix said, the JSON gem only handles double quoted strings (which is technically correct - JSON should be formatted with double quotes). This bit of code will clean that up before trying to parse it.
Recursive method:
require 'json'
def JSON.parse(source, opts = {})
r = JSON.parser.new(source, opts).parse
r = keys_to_symbol(r) if opts[:symbolize_names]
return r
end
def keys_to_symbol(h)
new_hash = {}
h.each do |k,v|
if v.class == String || v.class == Fixnum || v.class == Float
new_hash[k.to_sym] = v
elsif v.class == Hash
new_hash[k.to_sym] = keys_to_symbol(v)
elsif v.class == Array
new_hash[k.to_sym] = keys_to_symbol_array(v)
else
raise ArgumentError, "Type not supported: #{v.class}"
end
end
return new_hash
end
def keys_to_symbol_array(array)
new_array = []
array.each do |i|
if i.class == Hash
new_array << keys_to_symbol(i)
elsif i.class == Array
new_array << keys_to_symbol_array(i)
else
new_array << i
end
end
return new_array
end
Of course, there is a json gem, but that handles only double quotes.
Another way to handle this is to use YAML serialization/deserialization, which also preserves the format of the key:
YAML.load({test: {'test' => { ':test' => 5}}}.to_yaml)
=> {:test=>{"test"=>{":test"=>5}}}
Benefit of this approach it seems like a format that is better suited for REST services...
The most convenient way is by using the nice_hash gem: https://github.com/MarioRuiz/nice_hash
require 'nice_hash'
my_str = "{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }"
# on my_hash will have the json as a hash
my_hash = my_str.json
# or you can filter and get what you want
vals = my_str.json(:age, :city)
# even you can access the keys like this:
puts my_hash._user._location._city
puts my_hash.user.location.city
puts my_hash[:user][:location][:city]
If you think you might need both string and symbol keys:
JSON.parse(json_string).with_indifferent_access

Resources