attr_bucket 0.1.0 → 0.1.1

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 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