meta-ruby: how to dynamically call a scoped constant? - ruby

Say I have a class like:
class Person
module Health
GOOD = 10
SICK = 4
DEAD = 0
end
end
I can reference such Health codes like: Person::Health::GOOD. I'd like to dynamically generate a hash that maps from number values back to constant names:
{ 10 => "GOOD",
4 => "SICK",
0 => "DEAD" }
To do this dynamically, I've come up with:
Person::Health.constants.inject({}) do |hsh, const|
hsh.merge!( eval("Person::Health::#{const}") => const.to_s )
end
This works, but I wonder if there's a better/safer way to go about it. It's in a Rails app, and while it's nowhere near any user input, eval still makes me nervous. Is there a better solution?

You can use constants and const_get for this purpose.
ph = Person::Health # Shorthand
Hash[ph.constants(false).map { |c| [ph.const_get(c), c.to_s ] }]
# {10=>:GOOD, 4=>:SICK, 0=>:DEAD}
I added false to .constants to prevent including any inherited constants from included Modules. For example, without false the following scenario would also include a 5 => "X" mapping:
module A
X = 5
end
class Person
module Health
include A
# ...
end
end
Hash[ph.constants.map { |c| [ph.const_get(c), c.to_s ] }]
# {10=>"GOOD", 4=>"SICK", 0=>"DEAD", 5=>"X"}

Related

Sort a array of string by the reverse value

Right now I've produced the following code to sort a list of domains
domains = [
'api.test.google.com',
'dev.blue.google.com',
'dev.test.google.com',
'a.blue.google.com'
]
filtered = []
domains.each { |domain| filtered.push domain.reverse! }
domains.sort!
domains.each { |domain| filtered.push domain.reverse! }
The output of this code will be:
["a.blue.google.com", "dev.blue.google.com", "api.test.google.com", "dev.test.google.com"]
I'm trying to find a way to make this more elegant as it does not look like the most optimal solution to solve this problem but I'm having issues figuring out what is.
Thank you for your help!
Would this work for you?
domains.
map{|d| d.split(".")}.
sort_by(&:reverse).
map{|d| d.join(".") }
Edit: or indeed
domains.sort_by{|x| x.split(".").reverse}
Just to add, I think that something like this deserves to be a value object, as these are not simply strings and they have their own attributes and special behaviour (such as this sort).
For example:
class Domain
include Comparable
def initialize(string)
#string = string
end
def to_s
#string
end
def elements
#string.split(".")
end
protected def <=>(other)
elements.reverse <=> other.elements.reverse
end
def tld
elements.last
end
end
So you can then:
domains = [
Domain.new('api.test.google.com'),
Domain.new('dev.blue.google.com'),
Domain.new('dev.test.google.com'),
Domain.new('a.blue.google.com'),
]
domains.map(&:to_s)
=> ["api.test.google.com", "dev.blue.google.com", "dev.test.google.com", "a.blue.google.com"]
domains.sort.map(&:to_s)
=> ["a.blue.google.com", "dev.blue.google.com", "api.test.google.com", "dev.test.google.com"]
You can also add in any other behaviour you like, such as a method for returning the top level domain.
If all you want to do is sort by the reversed value use sort_by:
domains = [
'api.test.google.com',
'dev.blue.google.com',
'dev.test.google.com',
'a.blue.google.com'
]
domains.sort_by { |domain| domain.reverse }
#=> ["a.blue.google.com", "dev.blue.google.com", "api.test.google.com", "dev.test.google.com"]
If you are concerned with keeping the strings between the dots in the original order you can use:
domains.sort_by { |domain| domain.split('.').reverse }
#=> ["a.blue.google.com", "dev.blue.google.com", "api.test.google.com", "dev.test.google.com"]

How to get more records out of the Twitter gem?

I'm banging my head trying to understand how the Twitter gem's pagination works.
I've tried max_id and cursor and they both strangely don't work.
Basically the maximum I can get out of search results is 100, and I would like to get 500.
Current code:
max_page = 5
max_id = -1
#data = []
for i in (1..max_page)
t = twt_client.search("hello world", :count => 100, :result_type => :recent, :max_id => max_id)
t.each do | tweet |
#data << tweet
end
max_id = t.next_results[:max_id]
end
This actually tells me that next_results is a private method, anyone has a working solution?
Without knowing which gem you're referencing (please specify a URL), I'd say intiuitively that cursor and max_id wouldn't get you what you want. However count would. Since you say you're only retrieving 100 results and count is set to 100, that would make sense to me.
t = twt_client.search("hello world", :count => 500, :result_type => :recent, :max_id => max_id)
I'm assuming you're talking about the Twitter client referenced here. My first question is: What's twt_client and for that matter, what does its search method return? It's also possible that you've unwittingly updated the gem and there's been a code base change that makes your current script out of date.
Take a look at your installed gem version and another look at the README here.
Twitter::SearchResults#next_results is private, because they try to provide uniform interface for enumeration.
Look, there's included Twitter::Enumerable in search_results.rb
module Twitter
class SearchResults
include Twitter::Enumerable
...
private
def last?
!next_page?
end
...
def fetch_next_page
response = #client.send(#request_method, #path, next_page).body
self.attrs = response
end
...
end
end
And if you look at enumerable.rb, you'll see that method's Twitter::SearchResults#last? and Twitter::SearchResults#fetch_next_page are used by Twitter::SearchResults#each method
module Twitter
module Enumerable
include ::Enumerable
# #return [Enumerator]
def each(start = 0)
return to_enum(:each, start) unless block_given?
Array(#collection[start..-1]).each do |element|
yield(element)
end
unless last?
start = [#collection.size, start].max
fetch_next_page
each(start, &Proc.new)
end
self
end
...
end
end
And Twitter::SearchResults#each will iterate over pages until there's #attrs[:search_metadata][:next_results] in Twitter's responses. So you need to break iteration after you'll reach 500th element.
I think you just need to use each
#data = []
tweet_number = 1
search_results = twt_client.search("hello world", count: 100, result_type: :recent)
search_results.each do |tweet|
#data << tweet
break if tweet_number == 500
end
This post is a result of looking into gem's sources and twitter's api. I could make a mistake somewhere, since I haven't checked my thoughts in console.
Try this (I basically only updated the calculation of the max_id in the loop):
max_page = 5
max_id = -1
#data = []
for i in (1..max_page)
t = twt_client.search("hello world", :count => 100, :result_type => :recent, :max_id => max_id)
t.each do | tweet |
#data << tweet
end
max_id = t.to_a.map(&:id).max + 1 # or may be max_id = t.map(&:id).max + 1
end

Ruby: ActiveAdmin Gems: How can we retrieve all the registered resources?

Is it possible to retrieve all the registered resources for Active Admin interface?
I have registered a lot of resources in active admin and I want to have a list of registered resources so that it can put into my dropdown box.
Possible to do that?
i guarantee there must be a cleaner way, but you can access your Models that have been added as to AA by accessing the resources collection in the namespace you have defined. For example, if you have AA defined in the :admin namespace
> ActiveAdmin.application.namespace(:admin).resources.select {|r| r.respond_to? :resource_class_name}.map(&:resource_class_name)
will produce and array of class names..
=> ["::Activity", "::ActivityType", "::AdminUser"]
that gives you a list of items you can massage into a dropdown.
# I came up with this to generate a hash of
# :namespace => array of arrays containing 2 elements
# [title, link]
#
# With this <strikethrough>it should be trivial to</strikethrough> you can
# create a clickable select.
Hash[*
ActiveAdmin.
application.
namespaces.
collect{ |ns,v| [
ns,
v.resources.
keys.
select { |k|
r = ActiveAdmin.application.namespaces[ns].resources.find_by_key(k)
r.respond_to?(:resource_class_name) && !r.belongs_to?
}.
collect{|k|
[
k,
begin
Rails.application.routes.url_helpers.send(
(
"#{ns}_" +
k.underscore.pluralize.gsub('/','_') +
"_index_path"
).to_sym
)
rescue
Rails.application.routes.url_helpers.send(
(
"#{ns}_" +
k.underscore.pluralize.gsub('/','_') +
"_path"
).to_sym
)
end
]
}
]}.
flatten(1)
]

How to improve this Ruby case switch statement?

I am wondering if there's a more elegant way to say this in Ruby:
FREE_PLAN_MAXIMUM = 1
BASIC_PLAN_MAXIMUM = 10
PREMIUM_PLAN_MAXIMUM = 100
def maximum_entries_per_month
case plan
when "premium"
PREMIUM_PLAN_MAXIMUM
when "basic"
BASIC_PLAN_MAXIMUM
else
FREE_PLAN_MAXIMUM
end
end
I don't like the repetition of premium and basic inside the function. What might be an alternative?
It depends on the rest of your code, especially whether you're using those constants in other places. One pattern I've found nice for this kind of thing is a hash, though.
PLAN_MAXIMUMS = { free: 1, basic: 10, premium: 100 }
def maximum_entries_per_month
PLAN_MAXIMUMS[plan.to_sym] || PLAN_MAXIMUMS[:free]
end
Use Hash#fetch, which allows for a default value, instead of a case statement.
PLAN_MAXIMUMS = { free: 1, basic: 10, premium: 100 }
def maximum_entries_per_month
PLAN_MAXIMUMS.fetch(plan.to_sym, PLAN_MAXIMUMS[:free])
end
You don't need a method. Just have a hash:
maximum_entries_per_month = Hash.new(1).merge{"premium" => 100, "basic" => 10}
and call:
maximum_entries_per_month[plan]
what about:
FREE_PLAN_MAXIMUM = 1
BASIC_PLAN_MAXIMUM = 10
PREMIUM_PLAN_MAXIMUM = 100
PLANS = {'premium' => PREMIUM_PLAN_MAXIMUM, 'basic' => BASIC_PLAN_MAXIMUM, 'free' => FREE_PLAN_MAXIMUM}
def maximum_entries_per_month
PLANS[plan] or FREE_PLAN_MAXIMUM
end
that "or FREE_PLAN_MAXIMUM" will catch any plan that's not "premium", "basic" or "free", if you are sure you only have those three plans just remove that part
EDIT: this way you keep your other constants working
EDIT2: if you don't want to add more constants and you are sure plan is one of those, you can do:
def maximum_entries_per_month
self.class.const_get("#{plan.upcase}_PLAN_MAXIMUM")
end

How to execute some procedure in all ruby files in current directory?

I'm new to Ruby - I'm having troubles on every step...
Imagine a Ruby script main.rb and a lot of unknown script files script1.rb ... scriptN.rb.
Each scriptX.rb contains unique module with one procedure needs to be executed:
Module X
def some_procedure(i)
puts "{#i} Module X procedure executed successfully!"
end
end
All I need is to:
iterate over all files in current directory
if current file has name like /^script.*?\.rb$/
then load it and execute some_procedure
How can I do it in main.rb ?
Thank you in advance!
Choose from these great answers in SO on loading the files: Best way to require all files from a directory in ruby?
Then in your files, just have them execute on load, rather than on a method call.
The problem might be that, when a file is required, it doesn't return the list of modules (or, in general, constants) which it defines. So, unless you don't know which module a script has defined, you will not know where to pass your some_procedure message.
As a workaround, you may try getting the list of defined constants before and after the script was required, find a difference, i.e. list of constants during require, and iterate through all of them, checking which one implements the method you need.
First, we need to put some restriction:
Every file script_my1.rb will have the module named Script_my1. I.e. first letter capitalized, all other letters - lowercase.
Create two files script_my1.rb and script_my2.rb as follows:
---script_my1.rb:
module Script_my1
#value = 0
def self.some_procedure(i)
puts "#{i} my1 executed!"
#value = i
end
def self.another_procedure()
return #value
end
end
---script_my2.rb:
module Script_my2
#value = 0
def self.some_procedure(i)
puts "#{i} my2 executed!"
#value = i
end
def self.another_procedure()
return #value
end
end
Now the main script, that loads and executes some_procedure() in each module, and then another_procedure().
Please notice, that each module can have separated variables with the same name #value.
Moreover, I think every module can be executed in a separate thread and have access to global variables, but I have not tested it yet.
---main.rb:
# Load all files from the current directory
# with name like script_xxx.rb
i = 1
result = nil
Dir['./script_*.rb'].each { |f|
next if File.directory?(f)
require (f)
moduleName = f[2,f.length].rpartition('.rb')[0].capitalize
eval ( "#{moduleName}.some_procedure(%d)" % i )
eval ( "result = #{moduleName}.another_procedure()" )
puts result
i = i + 1
}
Output of this program is:
1 my1 executed!
1
2 my2 executed!
2
That is all!
Some improvement to previous solution can be made. If we want to avoid special naming, we can use global hash to store procedure's names. Each loaded script_xx.rb file would register it's own procedures in this global hash.
Please notice, that in this case we make two cycles:
first we load all files script_xx.b
every file while loading will register it's procedures in $global_procs array.
then iterate over all entries in $global_procs to execute all registered procedures via eval()
Hope, this is a more 'ruby-like' solution!
---script_my1.rb
module My1
#value = 0
def self.some_procedure(i)
puts "#{i} my1 executed!"
#value = i
end
def self.another_procedure()
return #value
end
end
$global_procs << { 'module' => 'My1',
'some_procedure' => 'My1.some_procedure',
'another_procedure' => 'My1.another_procedure' }
---script_my2.rb
module MMM2
#value = 0
def self.some_procedure(i)
puts "#{i} MMM2 executed!"
#value = i
end
def self.another_procedure()
return #value
end
end
$global_procs << { 'module' => 'MMM2',
'some_procedure' => 'MMM2.some_procedure',
'another_procedure' => 'MMM2.another_procedure' }
---main.rb
# Create global array for holding module's info
$global_procs = []
Dir['./script_*.rb'].each { |f|
next if File.directory?(f)
require (f)
}
i = 1
result = nil
$global_procs.each { |p|
puts "Module name: " + p['module']
eval(p['some_procedure']+'(i)')
result = eval(p['another_procedure']+'()')
puts result
i = i + 1
}

Resources