attr_bucket 0.1.1 → 0.1.2
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 +15 -9
- data/lib/attr_bucket/version.rb +1 -1
- data/lib/attr_bucket.rb +43 -4
- metadata +3 -3
data/README.rdoc
CHANGED
@@ -3,15 +3,16 @@
|
|
3
3
|
Sometimes you're tempted to use STI in your Rails app, but your STI classes
|
4
4
|
don't share all of the same attributes. So, you're left with two choices:
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
<b>1.</b> Add _all_ of the columns your descendant classes will need.
|
7
|
+
|
8
|
+
<b>2.</b> Remember that this occurrence is a telltale sign that STI is the wrong
|
9
|
+
design pattern to be using...
|
9
10
|
|
10
11
|
<b>OR IS IT?</b> Yes, it is. Probably.
|
11
12
|
|
12
13
|
But wait! I present you with a third option:
|
13
14
|
|
14
|
-
|
15
|
+
<b>3.</b> Give your model a bucket. So it can hold all its extra attributes.
|
15
16
|
|
16
17
|
Hoo boy. This is probably a horrible idea.
|
17
18
|
|
@@ -54,10 +55,13 @@ so we'll define our bucket with a hash, instead:
|
|
54
55
|
i_has_a_bucket :unique_traits => {
|
55
56
|
:in_possession_of_bucket => :boolean, # Has it been stolen yet?
|
56
57
|
:tusk_length_in_inches => :integer,
|
57
|
-
:also_known_as =>
|
58
|
+
:also_known_as => proc {|v| "aka #{v}"}
|
58
59
|
}
|
59
60
|
end
|
60
61
|
|
62
|
+
Note the use of the proc on the value side of the hash for :also_known_as. You can
|
63
|
+
supply any object that responds to +call+ for custom typecasting behavior.
|
64
|
+
|
61
65
|
Now, we can create some animals. Let's start with a crow.
|
62
66
|
|
63
67
|
crow = Bird.create(
|
@@ -132,6 +136,8 @@ Let's make sure the attributes got cast to the proper type:
|
|
132
136
|
=> 6
|
133
137
|
lolrus.in_possession_of_bucket
|
134
138
|
=> true
|
139
|
+
lolrus.also_known_as
|
140
|
+
=> "aka The Holder of the Bucket"
|
135
141
|
|
136
142
|
That lolrus looks to be in fine shape, indeed.
|
137
143
|
|
@@ -143,10 +149,10 @@ from an abstract class or something a bit, well, more sane.
|
|
143
149
|
|
144
150
|
attr_bucket isn't for you if:
|
145
151
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
152
|
+
* You ever intend to search against your custom attributes
|
153
|
+
* You plan to instantiate tons of records at a time (the serialization overhead
|
154
|
+
will hurt -- though it'll hurt less with psych)
|
155
|
+
* You want to get invited to all the cool software engineer parties
|
150
156
|
|
151
157
|
All that caveat-ing aside, give it a try, and let me know if you find a particularly
|
152
158
|
clever use case!
|
data/lib/attr_bucket/version.rb
CHANGED
data/lib/attr_bucket.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
module AttrBucket
|
2
|
-
def self.included(base)
|
2
|
+
def self.included(base) #:nodoc:
|
3
3
|
base.extend ClassMethods
|
4
4
|
end
|
5
5
|
|
6
6
|
private
|
7
7
|
|
8
|
+
# Retrieve the attribute bucket, or if it's not yet a Hash,
|
9
|
+
# initialize it as one.
|
8
10
|
def get_attr_bucket(name)
|
9
11
|
unless read_attribute(name).is_a?(Hash)
|
10
12
|
write_attribute(name, {})
|
@@ -12,8 +14,17 @@ module AttrBucket
|
|
12
14
|
read_attribute(name)
|
13
15
|
end
|
14
16
|
|
17
|
+
# Swipe the nifty column typecasting from the column class
|
18
|
+
# underlying the bucket column, or use the call method of
|
19
|
+
# the object supplied for +type+ if it responds to call.
|
20
|
+
#
|
21
|
+
# This allows custom typecasting by supplying a proc, etc
|
22
|
+
# as the value side of the hash in an attr_bucket definition.
|
15
23
|
def explicitly_type_cast(value, type, column_class)
|
16
24
|
return nil if value.nil?
|
25
|
+
|
26
|
+
return type.call(value) if type.respond_to?(:call)
|
27
|
+
|
17
28
|
case type
|
18
29
|
when :string then value.to_s
|
19
30
|
when :text then value.to_s
|
@@ -33,12 +44,40 @@ module AttrBucket
|
|
33
44
|
module ClassMethods
|
34
45
|
private
|
35
46
|
|
47
|
+
# Accepts an options hash of the format:
|
48
|
+
#
|
49
|
+
# :<bucket-name> => <bucket-attributes>,
|
50
|
+
# [:<bucket-name> => <bucket-attributes>], [...]
|
51
|
+
#
|
52
|
+
# where <tt><bucket-name></tt> is the +text+ column being used for
|
53
|
+
# serializing the objects, and <tt><bucket-attributes></tt> is:
|
54
|
+
#
|
55
|
+
# * A single attribute name in symbol format (not much point...)
|
56
|
+
# * An array of attribute names
|
57
|
+
# * A hash, in which the keys are the attribute names in Symbol format,
|
58
|
+
# and the values describe what they should be typecast as. The valid
|
59
|
+
# choices are +string+, +text+, +integer+, +float+, +decimal+, +datetime+,
|
60
|
+
# +timestamp+, +time+, +date+, +binary+, or +boolean+. This will invoke
|
61
|
+
# the same typecasting behavior as is normally used by the underlying
|
62
|
+
# ActiveRecord column (specific to your database).
|
63
|
+
#
|
64
|
+
# Alternately, you may specify a Proc or another object that responds to
|
65
|
+
# +call+, and it will be invoked with the value being assigned to the
|
66
|
+
# attribute as its only parameter, for custom typecasting behavior.
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
#
|
70
|
+
# attr_bucket :bucket => {
|
71
|
+
# :is_awesome => :boolean
|
72
|
+
# :circumference => :integer,
|
73
|
+
# :flanderized_name => proc {|val| "#{val}-diddly"}
|
74
|
+
# }
|
36
75
|
def attr_bucket(opts = {})
|
37
76
|
opts.map do |bucket_name, attrs|
|
38
77
|
bucket_column = self.columns_hash[bucket_name.to_s]
|
39
78
|
unless bucket_column.type == :text
|
40
79
|
raise ArgumentError,
|
41
|
-
"#{bucket_name} is
|
80
|
+
"#{bucket_name} is of type #{bucket_column.type}, not text"
|
42
81
|
end
|
43
82
|
serialize bucket_name, Hash
|
44
83
|
|
@@ -58,13 +97,13 @@ module AttrBucket
|
|
58
97
|
|
59
98
|
alias :i_has_a_bucket :attr_bucket
|
60
99
|
|
61
|
-
def define_bucket_reader(bucket_name, attr_name)
|
100
|
+
def define_bucket_reader(bucket_name, attr_name) #:nodoc:
|
62
101
|
define_method attr_name do
|
63
102
|
get_attr_bucket(bucket_name)[attr_name]
|
64
103
|
end unless method_defined? attr_name
|
65
104
|
end
|
66
105
|
|
67
|
-
def define_bucket_writer(bucket_name, attr_name, attr_type, column_class)
|
106
|
+
def define_bucket_writer(bucket_name, attr_name, attr_type, column_class) #:nodoc:
|
68
107
|
define_method "#{attr_name}=" do |val|
|
69
108
|
# TODO: Make this more resilient/granular for multiple bucketed changes
|
70
109
|
send("#{bucket_name}_will_change!")
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 2
|
9
|
+
version: 0.1.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ernie Miller
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-01-
|
17
|
+
date: 2011-01-28 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|