galetahub-enum_field 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +127 -0
- data/Rakefile +44 -0
- data/lib/enum_field.rb +22 -0
- data/lib/enum_field/builder.rb +78 -0
- data/lib/enum_field/define_enum.rb +17 -0
- data/lib/enum_field/enumerated_attribute.rb +88 -0
- data/lib/enum_field/railtie.rb +13 -0
- data/lib/enum_field/version.rb +3 -0
- metadata +75 -0
data/README.rdoc
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
= enum_field
|
2
|
+
|
3
|
+
* http://github.com/paraseba/enum_field
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Enables Active Record attributes to point to enum like objects, by saving in your database
|
8
|
+
only an integer ID.
|
9
|
+
|
10
|
+
|
11
|
+
== FEATURES:
|
12
|
+
|
13
|
+
* Allows creation of Classes with enum like behaviour.
|
14
|
+
* Allows any number of members and methods in the enum classes.
|
15
|
+
* Allows an integer id to be used in your database columns to link to the enum members (user.role_id)
|
16
|
+
* Enables higher abstraction interaction with +AR+ attributes:
|
17
|
+
* <code>user.role = Role.admin</code>
|
18
|
+
* <code>if user.role.can_edit?</code>
|
19
|
+
* Saves in your +AR+ tables, only an integer id pointing to the enumeration member.
|
20
|
+
|
21
|
+
== SYNOPSIS:
|
22
|
+
|
23
|
+
When in an Active Record class, you have an attribute like role, state or country you have
|
24
|
+
several options.
|
25
|
+
|
26
|
+
* You can create a roles, states or countries table, and dump there all possible values.
|
27
|
+
* You can use a string to identify, for instance, the role.
|
28
|
+
* You can use an id to identify the role.
|
29
|
+
|
30
|
+
If you are not confortable with any of this options, maybe +enum_field+ is an answer for you.
|
31
|
+
|
32
|
+
== BASIC USAGE:
|
33
|
+
|
34
|
+
class Role
|
35
|
+
define_enum do |builder|
|
36
|
+
builder.member :admin
|
37
|
+
builder.member :manager
|
38
|
+
builder.member :employee
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class User < ActiveRecord::Base
|
43
|
+
# in the database table there is a role_id integer column
|
44
|
+
enumerated_attribute :role
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
link_to_if(current_user.role == Role.admin, edit_foo_path(@foo))
|
49
|
+
|
50
|
+
user.role = Role.manager
|
51
|
+
user.role_id == Role.manager.id #will be true
|
52
|
+
|
53
|
+
User.first.role.id == User.first.role_id #will be true
|
54
|
+
|
55
|
+
Your enum classes can have all the methods you need:
|
56
|
+
|
57
|
+
class PhoneType
|
58
|
+
def initialize(name)
|
59
|
+
@name = name
|
60
|
+
end
|
61
|
+
attr_reader :name
|
62
|
+
|
63
|
+
define_enum do |b|
|
64
|
+
b.member :home, :object => PhoneType.new('home')
|
65
|
+
b.member :commercial, :object => PhoneType.new('commercial')
|
66
|
+
b.member :mobile, :object => PhoneType.new('mobile')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
user.phone.type.name
|
71
|
+
|
72
|
+
You have some +AR+ like methods in enum classes
|
73
|
+
|
74
|
+
PhoneType.all == [PhoneType.home, PhoneType.commercial, PhoneType.mobile] # ordered all
|
75
|
+
PhoneType.first == PhoneType.home
|
76
|
+
PhoneType.last == PhoneType.mobile
|
77
|
+
|
78
|
+
PhoneType.find_by_id(PhoneType.home.id) == PhoneType.home
|
79
|
+
PhoneType.find_by_id(123456) == nil
|
80
|
+
PhoneType.find(2) == PhoneType.commercial
|
81
|
+
PhoneType.find(123456) # will raise
|
82
|
+
|
83
|
+
The library also mimics has_many :through behavior, for cases such as:
|
84
|
+
|
85
|
+
class Role
|
86
|
+
define_enum do |builder|
|
87
|
+
builder.member :admin
|
88
|
+
builder.member :manager
|
89
|
+
builder.member :employee
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class User
|
94
|
+
has_many_enumerated_attributes :roles, :through => UserRole
|
95
|
+
end
|
96
|
+
|
97
|
+
class UserRole < ActiveRecord::Base
|
98
|
+
belongs_to :user
|
99
|
+
enumerated_attribute :role
|
100
|
+
end
|
101
|
+
|
102
|
+
user = User.create
|
103
|
+
user.role = [Role.manager, Role.admin]
|
104
|
+
user.roles.include?(Role.admin) #will be true
|
105
|
+
user.roles.include?(Role.manager) #will be true
|
106
|
+
user.roles.include?(Role.employee) #will be false
|
107
|
+
user.role_ids.include?(Role.manager.id) #will be true
|
108
|
+
user.role_ids = [Role.employee.id]
|
109
|
+
user.roles.include?(Role.employee) #will be true
|
110
|
+
user.roles.include?(Role.admin) #will be false
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
== REQUIREMENTS:
|
115
|
+
|
116
|
+
* activerecord
|
117
|
+
|
118
|
+
|
119
|
+
== INSTALL:
|
120
|
+
|
121
|
+
rails plugin install git://github.com/galetahub/enum_field.git
|
122
|
+
|
123
|
+
== LICENSE:
|
124
|
+
|
125
|
+
(The MIT License)
|
126
|
+
|
127
|
+
Copyright (c) 2009 Sebastián Bernardo Galkin
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require File.join(File.dirname(__FILE__), 'lib', 'enum_field', 'version')
|
6
|
+
|
7
|
+
desc 'Default: run unit tests.'
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
desc 'Test the enum_field plugin.'
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Generate documentation for the enum_field plugin.'
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'EnumField'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README.rdoc')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'jeweler'
|
29
|
+
Jeweler::Tasks.new do |s|
|
30
|
+
s.name = "galetahub-enum_field"
|
31
|
+
s.version = EnumField::VERSION
|
32
|
+
s.summary = "Enumerated attributes"
|
33
|
+
s.description = "Enables Active Record attributes to point to enum like objects, by saving in your database only an integer ID"
|
34
|
+
s.email = "galeta.igor@gmail.com"
|
35
|
+
s.homepage = "https://github.com/galetahub/enum_field"
|
36
|
+
s.authors = ["Igor Galeta", "Pavlo Galeta"]
|
37
|
+
s.files = FileList["[A-Z]*", "lib/**/*"]
|
38
|
+
s.extra_rdoc_files = FileList["[A-Z]*"] - %w(Rakefile)
|
39
|
+
end
|
40
|
+
|
41
|
+
Jeweler::GemcutterTasks.new
|
42
|
+
rescue LoadError
|
43
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
44
|
+
end
|
data/lib/enum_field.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module EnumField
|
3
|
+
autoload :DefineEnum, 'enum_field/define_enum'
|
4
|
+
autoload :Builder, 'enum_field/builder'
|
5
|
+
autoload :EnumeratedAttribute, 'enum_field/enumerated_attribute'
|
6
|
+
autoload :Version, 'enum_field/version'
|
7
|
+
|
8
|
+
class BadId < StandardError
|
9
|
+
def initialize(repeated_id)
|
10
|
+
@repeated_id = repeated_id
|
11
|
+
end
|
12
|
+
attr_reader :repeated_id
|
13
|
+
end
|
14
|
+
|
15
|
+
class RepeatedId < StandardError; end
|
16
|
+
class InvalidId < StandardError; end
|
17
|
+
class InvalidOptions < StandardError; end
|
18
|
+
class ObjectNotFound < StandardError; end
|
19
|
+
end
|
20
|
+
|
21
|
+
Module.send(:include, EnumField::DefineEnum)
|
22
|
+
require 'enum_field/railtie'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module EnumField
|
3
|
+
class Builder
|
4
|
+
def initialize(target)
|
5
|
+
@target = target
|
6
|
+
@next_id = 0
|
7
|
+
@id2obj = {}
|
8
|
+
@name2obj = {}
|
9
|
+
@sorted = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def member(name, options = {})
|
13
|
+
obj, candidate_id = process_options(options)
|
14
|
+
assign_id(obj, candidate_id)
|
15
|
+
define_in_meta(name) { obj }
|
16
|
+
save(name, obj)
|
17
|
+
obj.freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
def all
|
21
|
+
@sorted.dup
|
22
|
+
end
|
23
|
+
|
24
|
+
def names
|
25
|
+
@name2obj.keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def find(id)
|
29
|
+
find_by_id(id) or raise EnumField::ObjectNotFound
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_by_id(id)
|
33
|
+
@id2obj[id.to_i]
|
34
|
+
end
|
35
|
+
|
36
|
+
def first; @sorted.first; end
|
37
|
+
def last; @sorted.last; end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def define_in_meta(name, &block)
|
42
|
+
metaclass = class << @target; self; end
|
43
|
+
metaclass.send(:define_method, name, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def assign_id(obj, candidate_id)
|
47
|
+
id = new_id(candidate_id)
|
48
|
+
obj.class.send(:attr_reader, :id)
|
49
|
+
obj.instance_variable_set(:@id, id)
|
50
|
+
end
|
51
|
+
|
52
|
+
def new_id(candidate)
|
53
|
+
validate_candidate_id(candidate)
|
54
|
+
candidate || find_next_id
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate_candidate_id(id)
|
58
|
+
raise EnumField::InvalidId.new(id) unless id.nil? || id.is_a?(Integer) && id > 0
|
59
|
+
raise EnumField::RepeatedId.new(id) if @id2obj.has_key?(id)
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_next_id
|
63
|
+
@next_id += 1 while @id2obj.has_key?(@next_id) || @next_id <= 0
|
64
|
+
@next_id
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_options(options)
|
68
|
+
raise EnumField::InvalidOptions unless options.reject {|k,v| k == :object || k == :id}.empty?
|
69
|
+
[options[:object] || @target.new, options[:id]]
|
70
|
+
end
|
71
|
+
|
72
|
+
def save(name, obj)
|
73
|
+
@id2obj[obj.id] = obj
|
74
|
+
@sorted << obj
|
75
|
+
@name2obj[name] = obj
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module EnumField
|
3
|
+
module DefineEnum
|
4
|
+
def define_enum(&block)
|
5
|
+
@enum_builder ||= EnumField::Builder.new(self)
|
6
|
+
yield @enum_builder
|
7
|
+
|
8
|
+
[:all, :names, :find_by_id, :find, :first, :last].each do |method|
|
9
|
+
instance_eval <<-END
|
10
|
+
def #{method}(*args, &block)
|
11
|
+
@enum_builder.send(:#{method}, *args, &block)
|
12
|
+
end
|
13
|
+
END
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module EnumField
|
3
|
+
# Easies the inclusion of enumerations as ActiveRecord columns.
|
4
|
+
# If you have a User AR, with a role_id column, you could do:
|
5
|
+
# <tt>
|
6
|
+
# class User
|
7
|
+
# enumerated_attribute :role
|
8
|
+
# end
|
9
|
+
# </tt>
|
10
|
+
#
|
11
|
+
# This assumes a Role class, and will define role and role= methods in User class.
|
12
|
+
# These added methods expect an object of class Role, and Role should provide a find class method.
|
13
|
+
# You could get this by using Enumerated mixin
|
14
|
+
#
|
15
|
+
# Similar to has_many :through in AR, it creates reader and writter methods to get enumerated atributes going through an
|
16
|
+
# intermediate model.
|
17
|
+
# For instance:
|
18
|
+
# <tt>
|
19
|
+
# class User
|
20
|
+
# has_many_enumerated_attributes :roles, :through => UserRole
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# class UserRole < ActiveRecord::Base
|
24
|
+
# belongs_to :user
|
25
|
+
# enumerated_attribute :role
|
26
|
+
# end
|
27
|
+
# </tt>
|
28
|
+
# This assumes a Role class, the UserRole AR class is there just to persist the one-to-many relationship.
|
29
|
+
# This will define roles, roles=, role_ids and role_ids= methods in User class
|
30
|
+
|
31
|
+
module EnumeratedAttribute
|
32
|
+
# Define an enumerated field of the AR class
|
33
|
+
# * +name_attribute+: the name of the field that will be added, for instance +role+
|
34
|
+
# * +options+: Valid options are:
|
35
|
+
# * +id_attribute+: the name of the AR column where the enumerated id will be save. Defaults to
|
36
|
+
# +name_attribute+ with an +_id+ suffix.
|
37
|
+
# * +class+: the class that will be instantiated when +name_attribute+ method is called. Defaults to
|
38
|
+
# +name_attribute+ in camelcase form.
|
39
|
+
def enumerated_attribute(name_attribute, options = {})
|
40
|
+
id_attribute = options[:id_attribute] || (name_attribute.to_s + '_id').to_sym
|
41
|
+
klass = options[:class] || name_attribute.to_s.camelcase.constantize
|
42
|
+
define_method(name_attribute) do
|
43
|
+
(raw = read_attribute(id_attribute)) && klass.find_by_id(raw)
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method(name_attribute.to_s + '=') do |value|
|
47
|
+
write_attribute(id_attribute, value ? value.id : nil)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# alias of enumerated_attribute
|
52
|
+
alias belongs_to_enumerated_attribute enumerated_attribute
|
53
|
+
|
54
|
+
# Defines a one-to-many association between an AR class and the enumerated
|
55
|
+
# * +association+: the name of the one-to-many association, for instance +roles+
|
56
|
+
# * +options+: Valid options are:
|
57
|
+
# * +through+ : the name of the AR class needed to persist the one-to-many association.
|
58
|
+
# Defaults to AR class in camelcase form concatenated with the enumerated class in camelcase form.
|
59
|
+
# * +class+: the enumerated class, it will be instantiated +n+ times when +association+ method is called.
|
60
|
+
# Defaults to +association+ in singular camelcase form.
|
61
|
+
def has_many_enumerated_attributes(association, options = {})
|
62
|
+
enum_attr = association.to_s.singularize
|
63
|
+
klass = options[:class] || enum_attr.camelcase.constantize
|
64
|
+
through = options[:through] || (self.name + klass.name)
|
65
|
+
self_attribute = self.name.demodulize.underscore
|
66
|
+
association_ids = association.to_s.singularize + '_ids'
|
67
|
+
has_many_aux = through.demodulize.underscore.pluralize
|
68
|
+
|
69
|
+
has_many has_many_aux, {:class_name => through, :dependent => :destroy}
|
70
|
+
|
71
|
+
define_method(association) do
|
72
|
+
self.send(has_many_aux).map(&enum_attr.to_sym)
|
73
|
+
end
|
74
|
+
|
75
|
+
define_method(association.to_s + '=') do |values|
|
76
|
+
self.send(has_many_aux + '=', values.map{|g| through.constantize.new(self_attribute => self, enum_attr => g)})
|
77
|
+
end
|
78
|
+
|
79
|
+
define_method(association_ids) do
|
80
|
+
self.send(association).map(&:id)
|
81
|
+
end
|
82
|
+
|
83
|
+
define_method(association_ids + '=') do |values|
|
84
|
+
self.send(has_many_aux + '=', values.map{|g| g.to_i unless g.blank?}.compact.map{|g_id| through.constantize.new(self_attribute => self, enum_attr + '_id' => g_id) })
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rails'
|
3
|
+
require 'enum_field'
|
4
|
+
|
5
|
+
module EnumField
|
6
|
+
class Railtie < ::Rails::Railtie
|
7
|
+
config.before_initialize do
|
8
|
+
ActiveSupport.on_load :active_record do
|
9
|
+
ActiveRecord::Base.send(:extend, EnumField::EnumeratedAttribute)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: galetahub-enum_field
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 4
|
10
|
+
version: 0.1.4
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Igor Galeta
|
14
|
+
- Pavlo Galeta
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2011-06-16 00:00:00 +03:00
|
20
|
+
default_executable:
|
21
|
+
dependencies: []
|
22
|
+
|
23
|
+
description: Enables Active Record attributes to point to enum like objects, by saving in your database only an integer ID
|
24
|
+
email: galeta.igor@gmail.com
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files:
|
30
|
+
- README.rdoc
|
31
|
+
files:
|
32
|
+
- README.rdoc
|
33
|
+
- Rakefile
|
34
|
+
- lib/enum_field.rb
|
35
|
+
- lib/enum_field/builder.rb
|
36
|
+
- lib/enum_field/define_enum.rb
|
37
|
+
- lib/enum_field/enumerated_attribute.rb
|
38
|
+
- lib/enum_field/railtie.rb
|
39
|
+
- lib/enum_field/version.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: https://github.com/galetahub/enum_field
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 3
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.6.2
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Enumerated attributes
|
74
|
+
test_files: []
|
75
|
+
|