What Is the Advantage of Creating an Enumerable Object Using To_Enum in Ruby

Why use Array#to_enum when Array already is an Enumerable?

Because the Enumerable module is different from the Enumerator class.

Being "Enumerable" means that the class gets a bunch of freebie methods that create "Enumerators". Compare to Java's Iterable and Iterator interfaces.

How to implement Enums in Ruby?

Two ways. Symbols (:foo notation) or constants (FOO notation).

Symbols are appropriate when you want to enhance readability without littering code with literal strings.

postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"

Constants are appropriate when you have an underlying value that is important. Just declare a module to hold your constants and then declare the constants within that.

module Foo
BAR = 1
BAZ = 2
BIZ = 4
end

flags = Foo::BAR | Foo::BAZ # flags = 3

Added 2021-01-17

If you are passing the enum value around (for example, storing it in a database) and you need to be able to translate the value back into the symbol, there's a mashup of both approaches

COMMODITY_TYPE = {
currency: 1,
investment: 2,
}

def commodity_type_string(value)
COMMODITY_TYPE.key(value)
end

COMMODITY_TYPE[:currency]

This approach inspired by andrew-grimm's answer https://stackoverflow.com/a/5332950/13468

I'd also recommend reading through the rest of the answers here since there are a lot of ways to solve this and it really boils down to what it is about the other language's enum that you care about

Possibility of mapping enum values to string type instead of integer

As far as I know it's not possible with Active Record's built-in enum functionality. However, there are a few popular 3rd party gems that do this. The closest match to what comes with Active Record are probably enumerize and SimpleEnum.

However, if you're looking for something a little different, I'd recommend ClassyEnum (full disclosure: I wrote it). Here are some of my notes on the difference between enumerize and SimpleEnum vs ClassyEnum:

Class-less enums (enumerize, SimpleEnum) are great for simple use
cases where you just need a field to represent one of a fixed set of
values. The main issue I have with this solution is that it encourages
conditionals scattered throughout your models, controllers and views.
It's tempting to use these gems because they are the simplest to
implement, but the long term payoff just isn't there IMO for anything
but the simplest cases.

ClassyEnum was designed to solve the problem of having scattered
conditional logic related to the different enums. You can still use it
for simple collections that don't have logic, but when you do need to
add logic (and you almost certainly will), you can push that into the
individual enum classes and take advantage of polymorphism.

How can I display enum string key instead of integer value in SQL query?

You are asking the DB for info using SQL and so it will not have any knowledge of your Rails enums. You need to use Rails to make the query:

Animals.all.group(:sound).count(:sound)

=> {"bark"=>2, "meow"=>4, "moo"=>3}

For a pure sql answer with Postgresql:

SELECT temp.sound_count,
CASE
when temp.sound = 0 then 'bark'
when temp.sound = 1 then 'meow'
when temp.sound = 2 then 'moo'
END
AS my_sound
FROM (SELECT COUNT(s.sound) as sound_count, a.sound from animals a
GOUP BY a.sound)
AS temp;

How to handle enumerations without enum fields in a database?

Using a foreign key to a lookup table is the approach I use. In fact, I use this even when I do use a database that supports ENUM (e.g. MySQL).

For simplicity, I may skip the ever-present "id" for the lookup table, and just use the actual value I need in my main table as the primary key of the lookup table. That way you don't need to do a join to get the value.

CREATE TABLE BugStatus (
status VARCHAR(20) PRIMARY KEY
);

INSERT INTO BugStatus (status) VALUES ('NEW'), ('OPEN'), ('FIXED');

CREATE TABLE Bugs (
bug_id SERIAL PRIMARY KEY,
summary VARCHAR(80),
...
status VARCHAR(20) NOT NULL DEFAULT 'NEW',
FOREIGN KEY (status) REFERENCES BugStatus(status)
);

Admittedly, storing strings takes more space than MySQL's implementation of ENUM, but unless the table in question has millions of rows, it hardly matters.

Other advantages of the lookup table are that you can add or remove a value from the list with a simple INSERT or DELETE, whereas with ENUM you have to use ALTER TABLE to redefine the list.

Also try querying the current list of permitted values in an ENUM, for instance to populate a pick-list in your user interface. It's a major annoyance! With a lookup table, it's easy: SELECT status from BugStatus.

Also you can add other attribute columns to the lookup table if you need to (e.g. to mark choices available only to administrators). In an ENUM, you can't annotate the entries; they're just simple values.

Another option besides a lookup table would be to use CHECK constraints (provided the database supports them -- MySQL doesn't support CHECK until version 8.0.16):

CREATE TABLE Bugs (
bug_id SERIAL PRIMARY KEY,
summary VARCHAR(80),
...
status VARCHAR(20) NOT NULL
CHECK (status IN ('NEW', 'OPEN', 'FIXED'))
);

But this use of a CHECK constraint suffers from the same disadvantages as the ENUM: hard to change the list of values without ALTER TABLE, hard to query the list of permitted values, hard to annotate values.

PS: the equality comparison operator in SQL is a single =. The double == has no meaning in SQL.

Best way to enum NSString

OK, I answered myself. Guess I make a mistake.

This is the new feature I mentioned above:

typedef enum Language : NSUInteger{
ObjectiveC,
Java,
Ruby,
Python,
Erlang
}Language;

It's just a new syntax for enum in Xcode 4.4, but I'm so foolish to think we can exchange "NSUInteger" to "NSString".

So here is the way I found that works:

http://longweekendmobile.com/2010/12/01/not-so-nasty-enums-in-objective-c/

// Place this in your .h file, outside the @interface block
typedef enum {
JPG,
PNG,
GIF,
PVR
} kImageType;
#define kImageTypeArray @"JPEG", @"PNG", @"GIF", @"PowerVR", nil

...

// Place this in the .m file, inside the @implementation block
// A method to convert an enum to string
-(NSString*) imageTypeEnumToString:(kImageType)enumVal
{
NSArray *imageTypeArray = [[NSArray alloc] initWithObjects:kImageTypeArray];
return [imageTypeArray objectAtIndex:enumVal];
}

// A method to retrieve the int value from the NSArray of NSStrings
-(kImageType) imageTypeStringToEnum:(NSString*)strVal
{
NSArray *imageTypeArray = [[NSArray alloc] initWithObjects:kImageTypeArray];
NSUInteger n = [imageTypeArray indexOfObject:strVal];
if(n < 1) n = JPG;
return (kImageType) n;
}

FYI. The original author of the second example code created a category for enum handling. Just the thing for adding to your very own NSArray class definition.

@interface NSArray (EnumExtensions)

- (NSString*) stringWithEnum: (NSUInteger) enumVal;
- (NSUInteger) enumFromString: (NSString*) strVal default: (NSUInteger) def;
- (NSUInteger) enumFromString: (NSString*) strVal;

@end

@implementation NSArray (EnumExtensions)

- (NSString*) stringWithEnum: (NSUInteger) enumVal
{
return [self objectAtIndex:enumVal];
}

- (NSUInteger) enumFromString: (NSString*) strVal default: (NSUInteger) def
{
NSUInteger n = [self indexOfObject:strVal];
if(n == NSNotFound) n = def;
return n;
}

- (NSUInteger) enumFromString: (NSString*) strVal
{
return [self enumFromString:strVal default:0];
}

@end


Related Topics



Leave a reply



Submit