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 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
@@ -0,0 +1,3 @@
1
+ module EnumField
2
+ VERSION = "0.1.4".freeze
3
+ 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
+