How to move this to a base class, its a Ruby enumeration - ruby-on-rails

I read this article that explained how to make enumerations in Ruby, and also showed how to them enumerable like:
class Blah
def Blah.add_item(key, value)
#hash ||= {}
#hash[key] = value
end
def Blah.const_missing(key)
#hash[key]
end
def Blah.each
#hash.each {|key, value| yield(key, value)}
end
end
I have other enumerations that I need, can I create a base class somehow from this so I don't have to repeat the methods add_item, const_missng and .each for each one?
When creating a file for all my enums, I put it in /lib/enums.rb, is that a good practise?
should I be putting this class inside of a module i.e. I believe you do that for a namespace right?

You can just use Blah as your base class.
class C < Blah; end
class D < Blah; end
I think I might just throw it in with the source code of each project it's used with. Yes, DIE, DRY, and all that, but that's mostly important in line-by-line code. It's a fairly common practice to merge external software with each project.
No. It's already a class, so it's using only one name. Put the module around the code that uses Blah, the project or section of a project. That will be large and more in need of namespacing.

DigitalRoss's answer is good. I'll present an alternative. Suppose you'd like each of your enumerations to live in a module. All you need is a little Enumeration module, like so:
module Enumeration
include Enumerable
def self.included(m)
m.extend self
end
def each(&block)
constants.find_all do |name|
name =~ /^[A-Z_\d]+$/
end.map do |name|
[name, const_get(name)]
end.sort_by(&:last).each(&block)
end
end
When you need an enumeration, create a module for it, include Enumeration, and define your keys and values as constants with all-caps names.
module States
include Enumeration
INIT = 1
RUN = 2
DONE = 3
end
The module will respond to any of the methods provided by Enumerable:
p States.to_a
# => [["INIT", 1], ["RUN", 2], ["DONE", 3]]
You may find that you sometimes don't care what the values are, just that they are distinct. Let's add to Enumeration a method value that makes it easy to create constants with auto-incrementing keys:
module Enumeration
def value(name, value = next_value)
const_set(name, value)
end
def next_value
(map(&:last).max || 0) + 1
end
end
Now let's have some planets:
module Planets
include Enumeration
value :MERCURY
value :VENUS
value :EARTH
end
p Planets.to_a
# => [["MERCURY", 1], ["VENUS", 2], ["EARTH", 3]]
Of course, these enumerations are just collections of normal constants, so you can use them directly:
p Planets::MERCURY # => 1

Related

Creating a scope from an array constant

I have a situation where I have an array constant that I'd like to perform a string search on through a scope. I usually use AR to accomplish this but wasn't sure how to incorporate this with a static array. Obviously using a where clause wouldn't work here. What would be the best solution?
class Skills
SALES_SKILLS = %w(
Accounting
Mentoring
...
)
# Search above array based on "skill keyword"
scope :sales_skills, ->(skill) { }
end
May be using Enumerable#grep and convert string to case ignoring regexp with %r{} literal
class Skills
SALES_SKILLS = %w(
Accounting
Mentoring
#...
)
def self.sales_skills(skill)
SALES_SKILLS.grep(%r{#{skill}}i)
end
end
Skills.sales_skills('acc')
#=> ["Accounting"]
Skills.sales_skills('o')
#=> ["Accounting", "Mentoring"]
Skills.sales_skills('accounting')
#=> ["Accounting"]
Skills.sales_skills('foo')
#=> []
It would be better to create a method for this as you want to return a string. Scope is designed to return an ActiveRecord::Relation:
Scoping allows you to specify commonly-used queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as where, joins and includes. All scope bodies should return an ActiveRecord::Relation or nil to allow for further methods (such as other scopes) to be called on it.
Reference: https://guides.rubyonrails.org/active_record_querying.html#scopes
class Skills
SALES_SKILLS = %w(
Accounting
Mentoring
#...
)
def self.sales_skills(skill)
SALES_SKILLS.select do |sales_skill|
sales_skill.downcase.include?(skill.downcase)
end
end
end

Is it possible to access attr_reader variables like an array in RoR?

I have a class that looks like this:
class AwsAssets
attr_reader :AWS_INSTANCE, :AWS_INSTANCE_RESERVATION, :AWS_AVAILABILITY_ZONE
##AWS_INSTANCE = 1
##AWS_INSTANCE_RESERVATION = 2
##AWS_AVAILABILITY_ZONE = 3
end
I'm trying to access these variable ID's in another file. I want to do something along the lines of this:
def index
types = AwsAssets.attr_reader
#filter = "model_type_id NOT IN (#{types.join(', ')})"
end
This is obviously not correct syntax, I'm just wondering if I can access all the attr_reader variables somehow - I realize I could just put all of the attributes into an array, but there are like 100 variables that I would have to duplicate, which I would rather not do.
In your code you mixed couple of different properties that you can define on a class, so I am not sure which one you want to use.
You can use class instance variables
class AwsAssets
#AWS_INSTANCE = 1
#AWS_INSTANCE_RESERVATION = 2
#AWS_AVAILABILITY_ZONE = 3
end
AwsAssets.instance_variables
# => [:#AWS_INSTANCE, :#AWS_INSTANCE_RESERVATION, :#AWS_AVAILABILITY_ZONE]
AwsAssets.instance_variable_get "#AWS_INSTANCE"
# => 1
Or you can use class variables
class AwsAssets
##AWS_INSTANCE = 1
##AWS_INSTANCE_RESERVATION = 2
##AWS_AVAILABILITY_ZONE = 3
end
AwsAssets.class_variables
# [:##AWS_INSTANCE, :##AWS_INSTANCE_RESERVATION, :##AWS_AVAILABILITY_ZONE]
AwsAssets.class_variable_get "##AWS_INSTANCE""
# => 1
When you create class variable with ##, you will have the same value for this class and all subclasses. If you use class instance variables with # you can have different values for parent class and subclasses.
Some meta-programming could be helpful for you here.
class AwsAssets
# define your attributes here in a constant which is accessible from other classes
ATTRIBUTES = [:AWS_INSTANCE, :AWS_INSTANCE_RESERVATION, :AWS_AVAILABILITY_ZONE]
# use the definition above to call `attr_reader` for each attribute in the array
ATTRIBUTES.each { |attribute| attr_reader(attribute) }
# OR a shortcut: attr_reader(*ATTRIBUTES)
end
def index
# You can now access that array of attributes in other areas like so
types = AwsAssets.ATTRIBUTES
#filter = "model_type_id NOT IN (#{types.join(', ')})"
end
Here's a good resource for some more examples and information about meta-programming in Ruby: Introduction to Ruby Meta-Programming Techniques

How can I write a Ruby class to hold a pair or items?

Here is what I have so far, it is not working:
class Couple(o,t)
one = o
two = t
end
couple1 = Couple.new(10, "Ten")
p couple1.one
p couple1.two
Not sure why it's not working?
Defining a class doesn't work like defining functions, you have to define functions on them that use the internal variables, and the the initializer that tells it what to do when calling .new
attr_accessor helps with the crud of setting up the functions and variables. The most simplest ways would be to use have a class like
class Couple
attr_accessor :one, :two
end
couple1 = Couple.new
couple1.one = 10
couple1.two = "Ten"
p couple1.one
p couple1.two
To use the new function to initialize the class with a few variables, you can define that function giving you a Class definition looking like
class Couple
attr_accessor :one, :two
def initialize(one, two)
#one = one
#two = two
end
end
couple1 = Couple.new(10, "Ten")
p couple1.one
p couple1.two
If you need to just hold a pair of items, then use Struct. It's a simple generator of classes that contain only variables and accessors, and nothing else (similar co C/C++'s Struct).
Couple = Struct.new(:one, :two)
# Or more idiomatically
class Couple < Struct.new(:one, :two)
def to_s
"one: #{self.one}, two: #{self.two}"
end
end
couple1 = Couple.new(10, 'ten')
puts couple1 # one: 10, two: ten
couple1.one = 100
puts couple1 # one: 100, two: ten
Also, one very interesting thing in Ruby is that the class data/members, both instance and class/static ones are "private" -- you can access them from outside only via accessor methods, not directly, and Ruby gives you the possibility to quicky generate these methods with the macros atrr_accessor, attr_reader, and attr_writer.
class Couple
one = 'o'
two = 't'
end
p Couple.one # NoMethodError: undefined method `one' for Couple:Class
class Couple
def initialize(one, two)
#one = one
#two = two
end
end
c = Couple.new(10, 'ten')
p c.one # undefined method `one' for #<Couple:0x936d2d4 #one=10, #two="ten">
That's why you need the accessors.
You need to use attr_reader for read or attr_accessor for read/write to access the class variables. Your class should look like this:
class Couple
attr_accessor :one, :two
def initialize(one, two)
#one = one
#two = two
end
end
Using attr_accessor will create, in this case, the methods one, one=, two, two=. If you were to use attr_reader, it would create the methods one, two.
Using the example above code, you could have:
couple = Couple.new(5, 6)
p couple.one # Outputs 5
p couple.two # Outputs 6
couple.one = 7
p couple.one # Outputs 7
There is also attr_writer, which will give you the methods one=, two=, but this isn't what you're looking for in this case. It gives you write only access to a variable.

In Ruby, how to write a method to display any object's instance variable names and its values

Given any object in Ruby (on Rails), how can I write a method so that it will display that object's instance variable names and its values, like this:
#x: 1
#y: 2
#link_to_point: #<Point:0x10031b298 #y=20, #x=38>
(Update: inspect will do except for large object it is difficult to break down the variables from the 200 lines of output, like in Rails, when you request.inspect or self.inspect in the ActionView object)
I also want to be able to print <br> to the end of each instance variable's value so as to print them out nicely on a webpage.
the difficulty now seems to be that not every instance variable has an accessor, so it can't be called with obj.send(var_name)
(the var_name has the "#" removed, so "#x" becomes "x")
Update: I suppose using recursion, it can print out a more advanced version:
#<Point:0x10031b462>
#x: 1
#y: 2
#link_to_point: #<Point:0x10031b298>
#x=38
#y=20
I would probably write it like this:
class Object
def all_variables(root=true)
vars = {}
self.instance_variables.each do |var|
ivar = self.instance_variable_get(var)
vars[var] = [ivar, ivar.all_variables(false)]
end
root ? [self, vars] : vars
end
end
def string_variables(vars, lb="\n", indent="\t", current_indent="")
out = "#{vars[0].inspect}#{lb}"
current_indent += indent
out += vars[1].map do |var, ivar|
ivstr = string_variables(ivar, lb, indent, current_indent)
"#{current_indent}#{var}: #{ivstr}"
end.join
return out
end
def inspect_variables(obj, lb="\n", indent="\t", current_indent="")
string_variables(obj.all_variables, lb, indent, current_indent)
end
The Object#all_variables method produces an array containing (0) the given object and (1) a hash mapping instance variable names to arrays containing (0) the instance variable and (1) a hash mapping…. Thus, it gives you a nice recursive structure. The string_variables function prints out that hash nicely; inspect_variables is just a convenience wrapper. Thus, print inspect_variables(foo) gives you a newline-separated option, and print inspect_variables(foo, "<br />\n") gives you the version with HTML line breaks. If you want to specify the indent, you can do that too: print inspect_variables(foo, "\n", "|---") produces a (useless) faux-tree format instead of tab-based indenting.
There ought to be a sensible way to write an each_variable function to which you provide a callback (which wouldn't have to allocate the intermediate storage); I'll edit this answer to include it if I think of something. Edit 1: I thought of something.
Here's another way to write it, which I think is slightly nicer:
class Object
def each_variable(name=nil, depth=0, parent=nil, &block)
yield name, self, depth, parent
self.instance_variables.each do |var|
self.instance_variable_get(var).each_variable(var, depth+1, self, &block)
end
end
end
def inspect_variables(obj, nl="\n", indent="\t", sep=': ')
out = ''
obj.each_variable do |name, var, depth, _parent|
out += [indent*depth, name, name ? sep : '', var.inspect, nl].join
end
return out
end
The Object#each_variable method takes a number of optional arguments, which are not designed to be specified by the user; instead, they are used by the recursion to maintain state. The given block is passed (a) the name of the instance variable, or nil if the variable is the root of the recursion; (b) the variable; (c) the depth to which the recursion has descended; and (d), the parent of the current variable, or nil if said variable is the root of the recursion. The recursion is depth-first. The inspect_variables function uses this to build up a string. The obj argument is the object to iterate through; nl is the line separator; indent is the indentation to be applied at each level; and sep separates the name and the value.
Edit 2: This doesn't really add anything to the answer to your question, but: just to prove that we haven't lost anything in the reimplementation, here's a reimplementation of all_variables in terms of each_variables.
def all_variables(obj)
cur_depth = 0
root = [obj, {}]
tree = root
parents = []
prev = root
obj.each_variable do |name, var, depth, _parent|
next unless name
case depth <=> cur_depth
when -1 # We've gone back up
tree = parents.pop(cur_depth - depth)[0]
when +1 # We've gone down
parents << tree
tree = prev
else # We're at the same level
# Do nothing
end
cur_depth = depth
prev = tree[1][name] = [var, {}]
end
return root
end
I feel like it ought to be shorter, but that may not be possible; because we don't have the recursion now, we have to maintain the stack explicitly (in parents). But it is possible, so the each_variable method works just as well (and I think it's a little nicer).
I see... Antal must be giving the advanced version here...
the short version then probably is:
def p_each(obj)
obj.instance_variables.each do |v|
puts "#{v}: #{obj.instance_variable_get(v)}\n"
end
nil
end
or to return it as a string:
def sp_each(obj)
s = ""
obj.instance_variables.each do |v|
s += "#{v}: #{obj.instance_variable_get(v)}\n"
end
s
end
or shorter:
def sp_each(obj)
obj.instance_variables.map {|v| "#{v}: #{obj.instance_variable_get(v)}\n"}.join
end
This is a quick adaptation of a simple JSON emitter I wrote for another question:
class Object
def inspect!(indent=0)
return inspect if instance_variables.empty?
"#<#{self.class}:0x#{object_id.to_s(16)}\n#{' ' * indent+=1}#{
instance_variables.map {|var|
"#{var}: #{instance_variable_get(var).inspect!(indent)}"
}.join("\n#{' ' * indent}")
}\n#{' ' * indent-=1}>"
end
end
class Array
def inspect!(indent=0)
return '[]' if empty?
"[\n#{' ' * indent+=1}#{
map {|el| el.inspect!(indent) }.join(",\n#{' ' * indent}")
}\n#{' ' * indent-=1}]"
end
end
class Hash
def inspect!(indent=0)
return '{}' if empty?
"{\n#{' ' * indent+=1}#{
map {|k, v|
"#{k.inspect!(indent)} => #{v.inspect!(indent)}"
}.join(",\n#{' ' * indent}")
}\n#{' ' * indent-=1}}"
end
end
That's all the magic, really. Now we only need some simple defaults for some types where a full-on inspect doesn't really make sense (nil, false, true, numbers, etc.):
module InspectBang
def inspect!(indent=0)
inspect
end
end
[Numeric, Symbol, NilClass, TrueClass, FalseClass, String].each do |klass|
klass.send :include, InspectBang
end
Like this?
# Get the instance variables of an object
d = Date.new
d.instance_variables.each{|i| puts i + "<br />"}
Ruby Documentation on instance_variables.
The concept is commonly called "introspection", (to look into oneself).

Overriding Ruby's spaceship operator <=>

I am trying to override Ruby's <=> (spaceship) operator to sort apples and oranges so that apples come first sorted by weight, and oranges second, sorted by sweetness. Like so:
module Fruity
attr_accessor :weight, :sweetness
def <=>(other)
# use Array#<=> to compare the attributes
[self.weight, self.sweetness] <=> [other.weight, other.sweetness]
end
include Comparable
end
class Apple
include Fruity
def initialize(w)
self.weight = w
end
end
class Orange
include Fruity
def initialize(s)
self.sweetness = s
end
end
fruits = [Apple.new(2),Orange.new(4),Apple.new(6),Orange.new(9),Apple.new(1),Orange.new(22)]
p fruits
#should work?
p fruits.sort
But this does not work, can someone tell what I am doing wrong here, or a better way to do this?
Your problem is you are only initializing one of the properties on either side, the other one will still be nil. nil isn't handled in the Array#<=> method, which ends up killing the sort.
There are a few ways to handle the problem first would be something like this
[self.weight.to_i, self.sweetness.to_i] <=> [other.weight.to_i, other.sweetness.to_i]
nil.to_i gives you 0, which will let this work.
Probably late, nevertheless...
add the following monkeypatch
class Array
def to_i(default=Float::INFINITY)
self.map do |element|
element.nil? ? default : element.to_i
end
end
end
And change the body of Fruity::<=> to
[self.weight, self.sweetness].to_i <=> [other.weight, other.sweetness].to_i

Resources