-
Notifications
You must be signed in to change notification settings - Fork 0
/
README
123 lines (85 loc) · 5.49 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
Often times, we have an AR model that has a notion of position. Take, for example, employees in a company. You may want to assign a position to each employee such as CEO, VP of Engineering, Director of Marketing, Employee #1, #2, etc. Or, in the case of Mixbook, lets say you have a page in a book. The page can have a position like Front Cover, Back Cover, Page #1, etc. The more you think about, you'll realize that there other things that fall well into this paradigm.
My goal was to create a extension to AR to help with these kinds of a problem in an elegant, space efficient way. Many times, people end up creating new columns for each different type of position. For the case of employee, you might add a new column when you have a new position. It's easy to see how this can get unwieldy very fast. In my approach, I use the smallest possible footprint by modeling this paradigm using a 1-4 byte signed integer column. So if you only have a couple of positions, you could use a signed TinyInt and get all this functionality in a byte!
Here is how it works!
class Employee < ActiveRecord::Base
## Columns ##
# id: Integer
# corporate_position: Integer
# company_id: Integer
belongs_to :company
# Optional - custom position type
module PositionTypes
class Designer < ActiveRecord::Acts::Positionable::Types::Special
def name
"Some awesome designer"
end
end
end
acts_as_positionable(:column => "corporate_position") do |p|
# A normal number will refer to the "normal" employees
p.number
p.special(-1, "CEO")
p.special(-2, "CTO")
p.special(-3, "CFO")
p.special(-4, "VP Corporate Development")
p.special(-5, "VP Marketing", :sort_index => 1000000) # we don't like those guys, should come at end
# designer comes from custom the custom position type class defined above.
p.designer(-6, "Lead Designer") #
p.group(-100, "Executive Team", [:ceo, :cto, :cfo])
p.pattern(-200, "Odd", "@value.modulo(2)==1")
end
end
class Company < ActiveRecord::Base
## Columns ##
# id: Integer
has_many_with_position :employees
end
Values greater than zero are used to model a generic number, ie Employee #1, Page #1. However, the negative values are used to represent special positions, group positions, and pattern positions. Here is the functionality that we get.
QUESTION METHODS
employee = Employee.create!(:corporate_position => :ceo) # => #<Employee id: 2, corporate_position: -1, company_id: nil>
employee.ceo? # => true
employee.vp_corporate_development? # => false
employee.number?(4) # => false
employee.executive_team? # => true
All these methods are created on the model, the position, and the has many association associated with it:
company = Company.create! # => <Company id: 1>
company.employees << Employee.new(:corporate_position => :cto) # => [<Employee id: 3, corporate_position: -2, company_id: 1>]
company.employees.cto? # Does the company have any employees on it that are a cto? # => true
company.employees.first.cto? # Is the employee a cto? # => true
company.employees.first.corporate_position.cto? # Is the corporate_position of this employee that of a cto? # => true
The last two use cases are pretty much the same thing but are provided for convenience.
If you want to change position of an employee, there are a couple of ways to do this:
WRITE METHODS
employee.vp_marketing = true # => true
employee # => <Employee id: 2, corporate_position: -5, company_id: nil>
Or you can just set the column directly:
employee.corporate_position = :ceo # => :ceo
employee # => <Employee id: 2, corporate_position: -1, company_id: nil>
FINDER METHODS
Wouldn't it be cool to also be able to use the position in your finders as well? Check this out...
Employee.find_by_corporate_position(:ceo) # => #<Employee id: 2, corporate_position: -1, company_id: nil>
Employee.find_all_by_corporate_position(:ceo) # => [#<Employee id: 2, corporate_position: -1, company_id: nil>]
Employee.first(:conditions => {:corporate_position => :ceo}) # => #<Employee id: 2, corporate_position: -1, company_id: nil>
Each position type comes with basic methods for common use cases as well:
OTHER METHODS
position = Employee.positions[:cto] # => #<ActiveRecord::Acts::Positionable::Types::Special:0x366da70 @name="CTO", @_memoized_titleize=["The CTO"], @_memoized_to_sym=[:cto], @value=-2, @sort_index=-2, @_memoized_short_name=["CTO"]>
position.to_sym # => :cto # or position.keyword
position.to_i # => -2
position.titleize # => "The CTO"
position.short_name # => "CTO"
position.value # => -2
position.name # => "CTO"
POSITION TYPES
I've created two abstract position type classes with the rest extending from there:
Primitive
Number - basic number values. All numbers are greater then zero.
Special - represents a single non-number type (ie "CEO" or "CTO")
Complex
Group - create a group of other positions (ie "Executive Team").
Pattern - represents a pattern of positions (ie "Odd")
Function - derive a custom function to match the position (experimental)
The beauty of this system is that you can customize this AR plugin with additional functionality. In the example provided, we subclassed ActiveRecord::Acts::Positionable::Types::Special to create a new "designer" type:
Employee.new(:corporation_position => :lead_designer).name
# => "Some awesome designer"
This way we can overwrite any of the functionality from the core position classes as demonstrated above.
I haven't had a chance to package this into a plugin, but you can stick it into your lib folder and give it a go.