attr_bucket 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,7 +1,156 @@
1
1
  = attr_bucket
2
2
 
3
- This is probably a horrible idea.
3
+ Sometimes you're tempted to use STI in your Rails app, but your STI classes
4
+ don't share all of the same attributes. So, you're left with two choices:
4
5
 
5
- == Copyright
6
+ 1. Add _all_ of the columns your descendant classes will need.
7
+ 2. Remember that this occurrence is a telltale sign that STI is the wrong
8
+ design pattern to be using...
9
+
10
+ <b>OR IS IT?</b> Yes, it is. Probably.
11
+
12
+ But wait! I present you with a third option:
13
+
14
+ 3. Give your model a bucket. So it can hold all its extra attributes.
15
+
16
+ Hoo boy. This is probably a horrible idea.
17
+
18
+ == Usage
19
+
20
+ Let's say we have a table containing animals. Animals have some universal traits,
21
+ such as a number of legs. We want to be able to search on those. But they also have
22
+ some specific traits that we'd like to display on the animal's detail page. We
23
+ don't (and this part is important) care about searching on these traits, we just want
24
+ to display them. OK, let's have at it:
25
+
26
+ class Animal < ActiveRecord::Base
27
+ # t.string :type
28
+ # t.string :name
29
+ # t.integer :number_of_legs
30
+ # t.boolean :can_fly
31
+ # t.boolean :is_cuddly
32
+ # t.text :unique_traits
33
+ end
34
+
35
+ We'll only track one unique attribute for birds, for now. This will be a string,
36
+ because strings are the default type for attributes in a bucket.
37
+
38
+ class Bird < Animal
39
+ attr_bucket :unique_traits => :group_name # "Murder" of crows, for instance
40
+ end
41
+
42
+ We might want to know "fresh" or "salt" for fish, (and whether they go best with
43
+ rice or asparagus). These will also be strings.
44
+
45
+ class Fish < Animal
46
+ attr_bucket :unique_traits => [:best_served_with, :water_type]
47
+ end
48
+
49
+ Now, we have a special animal that we want to add some non-string attributes to,
50
+ so we'll define our bucket with a hash, instead:
51
+
52
+ class Lolrus < Animal
53
+ # Yes, this actually works. It's an alias. Come on, I had to.
54
+ i_has_a_bucket :unique_traits => {
55
+ :in_possession_of_bucket => :boolean, # Has it been stolen yet?
56
+ :tusk_length_in_inches => :integer,
57
+ :also_known_as => :string
58
+ }
59
+ end
60
+
61
+ Now, we can create some animals. Let's start with a crow.
62
+
63
+ crow = Bird.create(
64
+ :name => 'Crow',
65
+ :number_of_legs => 2,
66
+ :can_fly => true,
67
+ :is_cuddly => false,
68
+ :group_name => 'Murder'
69
+ )
70
+ => #<Bird id: 1, type: "Bird", name: "Crow", ...>
71
+
72
+ Looks about like we'd expect. Now we can retrieve our bucketed attributes by name,
73
+ without worrying about the bucket they're in.
74
+
75
+ crow.group_name
76
+ => "Murder"
77
+
78
+ Let's create a fish of some kind. How about a salmon?
79
+
80
+ salmon = Fish.create(
81
+ :name => 'Salmon',
82
+ :number_of_legs => 0,
83
+ :can_fly => false,
84
+ :is_cuddly => false,
85
+ :water_type => 'fresh',
86
+ :best_served_with => 'Pan-fried asparagus'
87
+ )
88
+ => #<Fish id: 3, type: "Fish", name: "Salmon", ...>
89
+
90
+ As before, we can retrieve the bucketed attribute:
91
+
92
+ salmon.best_served_with
93
+ => "Pan-fried asparagus"
94
+
95
+ We can also change the attribute using a standard attribute writer, so this works just
96
+ fine with Rails forms.
97
+
98
+ salmon.best_served_with = 'A light red wine'
99
+ => "A light red wine"
100
+ salmon.save
101
+ => true
102
+
103
+ Let's pull it back out of the database and make sure everything looks right:
104
+
105
+ fish = Fish.find 3
106
+ => #<Fish id: 3, type: "Fish", name: "Salmon", ...>
107
+ fish == salmon
108
+ => true
109
+
110
+ And it maintained our updated attribute.
111
+
112
+ fish.best_served_with
113
+ => "A light red wine"
114
+
115
+ Now let's try an animal with some typecasting -- I'll create using string values for
116
+ the bucketed attributes since that's what a Rails form would send:
117
+
118
+ lolrus = Lolrus.create(
119
+ :name => 'The LOLRUS',
120
+ :number_of_legs => 0, # Do flippers count as legs?
121
+ :can_fly => false,
122
+ :is_cuddly => true,
123
+ :also_known_as => 'The Holder of the Bucket',
124
+ :in_possession_of_bucket => 't',
125
+ :tusk_length_in_inches => '6'
126
+ )
127
+ => #<Lolrus id: 5, type: "Lolrus", name: "The LOLRUS", ...>
128
+
129
+ Let's make sure the attributes got cast to the proper type:
130
+
131
+ lolrus.tusk_length_in_inches
132
+ => 6
133
+ lolrus.in_possession_of_bucket
134
+ => true
135
+
136
+ That lolrus looks to be in fine shape, indeed.
137
+
138
+ == Caveats
139
+
140
+ This whole thing's a caveat, really. If you're looking to do this, be absolutely,
141
+ positively sure that you really want to bucket your attributes rather than inherit
142
+ from an abstract class or something a bit, well, more sane.
143
+
144
+ attr_bucket isn't for you if:
145
+
146
+ * You ever intend to search against your custom attributes
147
+ * You plan to instantiate tons of records at a time (the serialization overhead
148
+ will hurt -- though it'll hurt less with psych)
149
+ * You want to get invited to all the cool software engineer parties
150
+
151
+ All that caveat-ing aside, give it a try, and let me know if you find a particularly
152
+ clever use case!
153
+
154
+ == Copyright (or blame, depending on how you look at it)
6
155
 
7
156
  Copyright (c) 2011 {Ernie Miller}[http://metautonomo.us]. See LICENSE for details.
data/attr_bucket.gemspec CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Ernie Miller"]
10
10
  s.email = ["ernie@metautonomo.us"]
11
11
  s.homepage = "http://metautonomo.us"
12
- s.summary = %q{This is probably a horrible idea.}
13
- s.description = %q{A really, really, horrible idea.}
12
+ s.summary = %q{Your model can has a bucket (for its attributes).}
13
+ s.description = %q{Store a few extra (non-searchable) attributes away in a bucket. This is probably a horrible idea, but try it anyway.}
14
14
 
15
15
  s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.0"])
16
16
 
data/lib/attr_bucket.rb CHANGED
@@ -12,20 +12,20 @@ module AttrBucket
12
12
  read_attribute(name)
13
13
  end
14
14
 
15
- def explicitly_type_cast(value, type)
15
+ def explicitly_type_cast(value, type, column_class)
16
16
  return nil if value.nil?
17
17
  case type
18
18
  when :string then value.to_s
19
19
  when :text then value.to_s
20
20
  when :integer then value.to_i rescue value ? 1 : 0
21
21
  when :float then value.to_f
22
- when :decimal then self.class.value_to_decimal(value)
23
- when :datetime then self.class.string_to_time(value)
24
- when :timestamp then self.class.string_to_time(value)
25
- when :time then self.class.string_to_dummy_time(value)
26
- when :date then self.class.string_to_date(value)
27
- when :binary then self.class.binary_to_string(value)
28
- when :boolean then self.class.value_to_boolean(value)
22
+ when :decimal then column_class.value_to_decimal(value)
23
+ when :datetime then column_class.string_to_time(value)
24
+ when :timestamp then column_class.string_to_time(value)
25
+ when :time then column_class.string_to_dummy_time(value)
26
+ when :date then column_class.string_to_date(value)
27
+ when :binary then column_class.binary_to_string(value)
28
+ when :boolean then column_class.value_to_boolean(value)
29
29
  else value
30
30
  end
31
31
  end
@@ -35,33 +35,40 @@ module AttrBucket
35
35
 
36
36
  def attr_bucket(opts = {})
37
37
  opts.map do |bucket_name, attrs|
38
+ bucket_column = self.columns_hash[bucket_name.to_s]
39
+ unless bucket_column.type == :text
40
+ raise ArgumentError,
41
+ "#{bucket_name} is a #{bucket_column.type} column, not text"
42
+ end
38
43
  serialize bucket_name, Hash
39
44
 
40
45
  if attrs.is_a?(Hash)
41
46
  attrs.map do|attr_name, attr_type|
42
47
  define_bucket_reader bucket_name, attr_name
43
- define_bucket_writer bucket_name, attr_name, attr_type
48
+ define_bucket_writer bucket_name, attr_name, attr_type, bucket_column.class
44
49
  end
45
50
  else
46
51
  Array.wrap(attrs).each do |attr_name|
47
52
  define_bucket_reader bucket_name, attr_name
48
- define_bucket_writer bucket_name, attr_name, :string
53
+ define_bucket_writer bucket_name, attr_name, :string, bucket_column.class
49
54
  end
50
55
  end
51
56
  end
52
57
  end
53
58
 
59
+ alias :i_has_a_bucket :attr_bucket
60
+
54
61
  def define_bucket_reader(bucket_name, attr_name)
55
62
  define_method attr_name do
56
63
  get_attr_bucket(bucket_name)[attr_name]
57
64
  end unless method_defined? attr_name
58
65
  end
59
66
 
60
- def define_bucket_writer(bucket_name, attr_name, attr_type)
67
+ def define_bucket_writer(bucket_name, attr_name, attr_type, column_class)
61
68
  define_method "#{attr_name}=" do |val|
62
69
  # TODO: Make this more resilient/granular for multiple bucketed changes
63
70
  send("#{bucket_name}_will_change!")
64
- get_attr_bucket(bucket_name)[attr_name] = explicitly_type_cast(val, attr_type)
71
+ get_attr_bucket(bucket_name)[attr_name] = explicitly_type_cast(val, attr_type, column_class)
65
72
  end unless method_defined? "#{attr_name}="
66
73
  end
67
74
  end
@@ -1,3 +1,3 @@
1
1
  module AttrBucket
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 0
9
- version: 0.1.0
8
+ - 1
9
+ version: 0.1.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ernie Miller
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: 3.0.0
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
- description: A really, really, horrible idea.
35
+ description: Store a few extra (non-searchable) attributes away in a bucket. This is probably a horrible idea, but try it anyway.
36
36
  email:
37
37
  - ernie@metautonomo.us
38
38
  executables: []
@@ -81,6 +81,6 @@ rubyforge_project: attr_bucket
81
81
  rubygems_version: 1.3.7
82
82
  signing_key:
83
83
  specification_version: 3
84
- summary: This is probably a horrible idea.
84
+ summary: Your model can has a bucket (for its attributes).
85
85
  test_files: []
86
86