Simplifying an "any of" check with or-operator in Ruby - ruby

How to simplify the following check ?...
if node[:base][:database][:adapter].empty? || node[:base][:database][:host].empty? ||
node[:base][:database][:database].empty? || node[:base][:database][:port].empty?
to something like
required_keys = { :adapter, :host, :database...etc...}
required keys - node[:base][:database] == []
This syntax is a little off, but basically subtract the keys you have from the set of required keys. If you have all the required keys in your set, the result should be empty.
I am not sure regarding the correct syntax ? . Any help would be appreciated

required_keys = [:adapter, :host, :database ]
if required_keys.any?{|x| node[:base][:database][x].empty?}
#code here
end
Or you could do also:
node[:base][:database].values_at(*required_keys).any?(&:empty?)

If you think you're going to use this functionality multiple places, you can extend the Hash class and require the extension in an initializer.
class Hash
def contains_values_for?(*keys)
keys.all? do |key|
self[key].present?
end
end
end
Now you can do:
node[:base][:database].contains_values_for?(:adapter, :host, :database, :port)

Related

Is multiple include?() arguments in ruby possible?

def coffee_drink?(drink_list)
drink_list.include?("coffee") ? true : drink_list.include?("espresso") ? true : false
end
I am learning Ruby with TOP and am looking to check for more than a single argument with the include function. I don't think my solution is too bad but surely there is a better solution I am just unable to find.
e.g. include?("ABC", "CBA) and include?("ABC" || "CBA") wont work.
def coffee_drink?(drink_list)
%w(coffee espresso).any? { |drink| drink_list.include?(drink) }
end
or
def coffee_drink?(drink_list)
(%w(coffee espresso) & drink_list).any?
end
note that your version could be rewritten like this
def coffee_drink?(drink_list)
drink_list.include?("coffee") || drink_list.include?("espresso")
end

Implement array and iterate

I have this Ruby code that I want to use:
if args[:remove_existing_trxs] == 'true'
Acquirer.delete_all
Company.delete_all
Currency.delete_all
AdminUser.delete_all
BaseReseller.delete_all
Terminal.delete_all
Contract.delete_all
Merchant.delete_all
MerchantUser.delete_all
PaymentTransaction.delete_all
end
How can I define it as an array and iterate?
Something like that?
[Model1, Model2].each do |model|
model.public_send(:delete_all)
end
Or with using Symbol#to_proc:
[Model1, Model2].each(&:delete_all)
try this out:
if args[:remove_existing_trxs] == 'true'
[Acquirer, Company, Currency, AdminUser,
BaseReseller, Terminal, Contract, Merchant,
MerchantUser, PaymentTransaction].each(&:destroy_all)
end

Select a record based on translated name

on ruby console when I do Resource.all it give me the following:
[<Resource id:'...', name_translated:{"en"=>'vehicle',"fr"=>'véhicule'}> ...]
How do I make a selection such that Resource.find_by_name_translated("vehicle")
This would work, I think it's not the most efficient way though:
#app/models/resource.rb
def self.find_by_english_name(name)
Resource.all.select do |resource|
resource.name_translated['en'] == name
end
end
if you want to be able to find by multiple languages (defaulting to english) with one method, try this:
def self.find_by_name(name, language = 'en')
Resource.all.select do |resource|
resource.name_translated[language] == name
end
end
Since you're using Postgres this can also be written as follows:
def self.find_by_name(name, language = 'en')
Resource.where("name_translated ->> '#{language}' = '#{name}'")
end
I'd use regex query if your db doesn't allow to query by json fields:
Resource.where("name_translated LIKE '#{translated_name}%'")

List dynamic attributes in a Mongoid Model

I have gone over the documentation, and I can't find a specific way to go about this. I have already added some dynamic attributes to a model, and I would like to be able to iterate over all of them.
So, for a concrete example:
class Order
include Mongoid::Document
field :status, type: String, default: "pending"
end
And then I do the following:
Order.new(status: "processed", internal_id: "1111")
And later I want to come back and be able to get a list/array of all the dynamic attributes (in this case, "internal_id" is it).
I'm still digging, but I'd love to hear if anyone else has solved this already.
Just include something like this in your model:
module DynamicAttributeSupport
def self.included(base)
base.send :include, InstanceMethods
end
module InstanceMethods
def dynamic_attributes
attributes.keys - _protected_attributes[:default].to_a - fields.keys
end
def static_attributes
fields.keys - dynamic_attributes
end
end
end
and here is a spec to go with it:
require 'spec_helper'
describe "dynamic attributes" do
class DynamicAttributeModel
include Mongoid::Document
include DynamicAttributeSupport
field :defined_field, type: String
end
it "provides dynamic_attribute helper" do
d = DynamicAttributeModel.new(age: 45, defined_field: 'George')
d.dynamic_attributes.should == ['age']
end
it "has static attributes" do
d = DynamicAttributeModel.new(foo: 'bar')
d.static_attributes.should include('defined_field')
d.static_attributes.should_not include('foo')
end
it "allows creation with dynamic attributes" do
d = DynamicAttributeModel.create(age: 99, blood_type: 'A')
d = DynamicAttributeModel.find(d.id)
d.age.should == 99
d.blood_type.should == 'A'
d.dynamic_attributes.should == ['age', 'blood_type']
end
end
this will give you only the dynamic field names for a given record x:
dynamic_attribute_names = x.attributes.keys - x.fields.keys
if you use additional Mongoid features, you need to subtract the fields associated with those features:
e.g. for Mongoid::Versioning :
dynamic_attribute_names = (x.attributes.keys - x.fields.keys) - ['versions']
To get the key/value pairs for only the dynamic attributes:
make sure to clone the result of attributes(), otherwise you modify x !!
attr_hash = x.attributes.clone #### make sure to clone this, otherwise you modify x !!
dyn_attr_hash = attr_hash.delete_if{|k,v| ! dynamic_attribute_names.include?(k)}
or in one line:
x.attributes.clone.delete_if{|k,v| ! dynamic_attribute_names.include?(k)}
So, what I ended up doing is this. I'm not sure if it's the best way to go about it, but it seems to give me the results I'm looking for.
class Order
def dynamic_attributes
self.attributes.delete_if { |attribute|
self.fields.keys.member? attribute
}
end
end
Attributes appears to be a list of the actual attributes on the object, while fields appears to be a hash of the fields that were predefined. Couldn't exactly find that in the documentation, but I'm going with it for now unless someone else knows of a better way!
try .methods or .instance_variables
Not sure if I liked the clone approach, so I wrote one too. From this you could easily build a hash of the content too. This merely outputs it all the dynamic fields (flat structure)
(d.attributes.keys - d.fields.keys).each {|a| puts "#{a} = #{d[a]}"};
I wasn't able to get any of the above solutions to work (as I didn't want to have to add slabs and slabs of code to each model, and, for some reason, the attributes method does not exist on a model instance, for me. :/), so I decided to write my own helper to do this for me. Please note that this method includes both dynamic and predefined fields.
helpers/mongoid_attribute_helper.rb:
module MongoidAttributeHelper
def self.included(base)
base.extend(AttributeMethods)
end
module AttributeMethods
def get_all_attributes
map = %Q{
function() {
for(var key in this)
{
emit(key, null);
}
}
}
reduce = %Q{
function(key, value) {
return null;
}
}
hashedResults = self.map_reduce(map, reduce).out(inline: true) # Returns an array of Hashes (i.e. {"_id"=>"EmailAddress", "value"=>nil} )
# Build an array of just the "_id"s.
results = Array.new
hashedResults.each do |value|
results << value["_id"]
end
return results
end
end
end
models/user.rb:
class User
include Mongoid::Document
include MongoidAttributeHelper
...
end
Once I've added the aforementioned include (include MongoidAttributeHelper) to each model which I would like to use this method with, I can get a list of all fields using User.get_all_attributes.
Granted, this may not be the most efficient or elegant of methods, but it definitely works. :)

merging similar hashes in ruby?

I've tried and tried, but I can't make this less ugly/more ruby-like. It seems like there just must be a better way. Help me learn.
class Df
attr_accessor :thresh
attr_reader :dfo
def initialize
#dfo = []
#df = '/opt/TWWfsw/bin/gdf'
case RUBY_PLATFORM
when /hpux/i
#fstyp = 'vxfs'
when /solaris/i
# fix: need /tmp too
#fstyp = 'ufs'
when /linux/i
#df = '/bin/df'
#fstyp = 'ext3'
end
#dfo = parsedf
end
def parsedf
ldf = []
[" "," -i"] .each do |arg|
fields = %w{device size used avail capp mount}
fields = %w{device inodes inodesused inodesavail iusep mount} if arg == ' -i'
ldf.push %x{#{#df} -P -t #{#fstyp}#{arg}}.split(/\n/)[1..-1].collect{|line| Hash[*fields.zip(line.split).flatten]}
end
out = []
# surely there must be an easier way
ldf[0].each do |x|
ldf[1].select { |y|
if y['device'] == x['device']
out.push x.merge(y)
end
}
end
out
end
end
In my machine, your ldf array after the df calls yields the following:
irb(main):011:0> ldf
=> [[{"device"=>"/dev/sda5", "size"=>"49399372", "mount"=>"/", "avail"=>"22728988", "used"=>"24161036", "capp"=>"52%"}], [{"device"=>"/dev/sda5", "inodes"=>"3137536", "mount"=>"/", "iusep"=>"13%", "inodesavail"=>"2752040", "inodesused"=>"385496"}]]
The most flexible approach to merging such a structure is probably something along these lines:
irb(main):013:0> ldf.flatten.inject {|a,b| a.merge(b)}
=> {"device"=>"/dev/sda5", "inodes"=>"3137536", "size"=>"49399372", "mount"=>"/", "avail"=>"22728988", "inodesavail"=>"2752040", "iusep"=>"13%", "used"=>"24161036", "capp"=>"52%", "inodesused"=>"385496"}
Some ruby programmers frown on this use of inject, but I like it, so your mileage may vary.
As for helping making your code more ruby like, I suggest you talk to some experienced rubyist you might know over your code to help you rewriting it in a way that follows good style and best practices. Probably that would the preferable than to just have someone rewrite it for you here.
Best of Luck!
Didn't test the code, but here goes:
ARGUMENTS = {
" " => %w{size used avail capp mount},
" -i" => %w{inodes inodesused inodesavail iusep mount}
}
def parsedf
# Store resulting info in a hash:
device_info = Hash.new do |h, dev|
h[dev] = {} # Each value will be a empty hash by default
end
ARGUMENTS.each do |arg, fields|
%x{#{#df} -P -t #{#fstyp}#{arg}}.split(/\n/)[1..-1].each do |line|
device, *data = line.split
device_info[device].merge! Hash[fields.zip(data)]
end
end
device_info
end
Notes: returns something a bit different than what you had:
{ "/dev/sda5" => {"inodes" => "...", ...},
"other device" => {...}
}
Also, I'm assuming Ruby 1.8.7 or better for Hash[key_value_pairs], otherwise you can resort to the Hash[*key_value_pairs.flatten] form you had
Depending on your needs, you should consider switch the fields from string to symbols; they are the best type of keys.

Resources