active_column 0.0.2 → 0.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/.gitignore +1 -0
- data/.yardopts +1 -0
- data/Gemfile.lock +120 -17
- data/README.md +27 -143
- data/active_column.gemspec +12 -6
- data/docs/Create.md +101 -0
- data/docs/Migrate.md +100 -0
- data/docs/Query.md +43 -0
- data/lib/active_column.rb +22 -3
- data/lib/active_column/base.rb +32 -46
- data/lib/active_column/errors.rb +6 -0
- data/lib/active_column/generators/migration_generator.rb +31 -0
- data/lib/active_column/generators/templates/migration.rb.erb +11 -0
- data/lib/active_column/key_config.rb +16 -0
- data/lib/active_column/migration.rb +269 -0
- data/lib/active_column/tasks/column_family.rb +64 -0
- data/lib/active_column/tasks/keyspace.rb +59 -0
- data/lib/active_column/tasks/ks.rb +76 -0
- data/lib/active_column/version.rb +1 -1
- data/spec/active_column/base_crud_spec.rb +1 -1
- data/spec/active_column/base_finders_spec.rb +6 -6
- data/spec/active_column/migrator_spec.rb +150 -0
- data/spec/active_column/tasks/column_family_spec.rb +34 -0
- data/spec/active_column/tasks/keyspace_spec.rb +38 -0
- data/spec/spec_helper.rb +24 -4
- data/spec/support/aggregating_tweet.rb +3 -1
- data/spec/support/migrate/migrator_spec/1_migration1.rb +11 -0
- data/spec/support/migrate/migrator_spec/2_migration2.rb +11 -0
- data/spec/support/migrate/migrator_spec/3_migration3.rb +11 -0
- data/spec/support/migrate/migrator_spec/4_migration4.rb +11 -0
- data/spec/support/tweet.rb +4 -2
- data/spec/support/tweet_dm.rb +6 -4
- metadata +103 -11
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private --protected - docs/*.md
|
data/Gemfile.lock
CHANGED
@@ -1,38 +1,141 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
active_column (0.0.
|
5
|
-
|
4
|
+
active_column (0.0.3)
|
5
|
+
activesupport
|
6
|
+
cassandra (>= 0.9)
|
7
|
+
rake
|
6
8
|
|
7
9
|
GEM
|
8
10
|
remote: http://rubygems.org/
|
9
11
|
specs:
|
10
|
-
|
12
|
+
ParseTree (3.0.6)
|
13
|
+
RubyInline (>= 3.7.0)
|
14
|
+
sexp_processor (>= 3.0.0)
|
15
|
+
RubyInline (3.8.6)
|
16
|
+
ZenTest (~> 4.3)
|
17
|
+
ZenTest (4.4.2)
|
18
|
+
abstract (1.0.0)
|
19
|
+
actionmailer (3.0.3)
|
20
|
+
actionpack (= 3.0.3)
|
21
|
+
mail (~> 2.2.9)
|
22
|
+
actionpack (3.0.3)
|
23
|
+
activemodel (= 3.0.3)
|
24
|
+
activesupport (= 3.0.3)
|
25
|
+
builder (~> 2.1.2)
|
26
|
+
erubis (~> 2.6.6)
|
27
|
+
i18n (~> 0.4)
|
28
|
+
rack (~> 1.2.1)
|
29
|
+
rack-mount (~> 0.6.13)
|
30
|
+
rack-test (~> 0.5.6)
|
31
|
+
tzinfo (~> 0.3.23)
|
32
|
+
activemodel (3.0.3)
|
33
|
+
activesupport (= 3.0.3)
|
34
|
+
builder (~> 2.1.2)
|
35
|
+
i18n (~> 0.4)
|
36
|
+
activerecord (3.0.3)
|
37
|
+
activemodel (= 3.0.3)
|
38
|
+
activesupport (= 3.0.3)
|
39
|
+
arel (~> 2.0.2)
|
40
|
+
tzinfo (~> 0.3.23)
|
41
|
+
activeresource (3.0.3)
|
42
|
+
activemodel (= 3.0.3)
|
43
|
+
activesupport (= 3.0.3)
|
44
|
+
activesupport (3.0.3)
|
45
|
+
arel (2.0.6)
|
46
|
+
bluecloth (2.0.9)
|
47
|
+
builder (2.1.2)
|
48
|
+
cassandra (0.9.0)
|
11
49
|
json
|
12
50
|
rake
|
13
51
|
simple_uuid (>= 0.1.0)
|
14
|
-
thrift_client (>= 0.
|
52
|
+
thrift_client (>= 0.6.0)
|
15
53
|
diff-lcs (1.1.2)
|
54
|
+
erubis (2.6.6)
|
55
|
+
abstract (>= 1.0.0)
|
56
|
+
file-tail (1.0.5)
|
57
|
+
spruz (>= 0.1.0)
|
58
|
+
i18n (0.5.0)
|
16
59
|
json (1.4.6)
|
60
|
+
mail (2.2.12)
|
61
|
+
activesupport (>= 2.3.6)
|
62
|
+
i18n (>= 0.4.0)
|
63
|
+
mime-types (~> 1.16)
|
64
|
+
treetop (~> 1.4.8)
|
65
|
+
mime-types (1.16)
|
66
|
+
polyglot (0.3.1)
|
67
|
+
predicated (0.2.2)
|
68
|
+
rack (1.2.1)
|
69
|
+
rack-mount (0.6.13)
|
70
|
+
rack (>= 1.0.0)
|
71
|
+
rack-test (0.5.6)
|
72
|
+
rack (>= 1.0)
|
73
|
+
rails (3.0.3)
|
74
|
+
actionmailer (= 3.0.3)
|
75
|
+
actionpack (= 3.0.3)
|
76
|
+
activerecord (= 3.0.3)
|
77
|
+
activeresource (= 3.0.3)
|
78
|
+
activesupport (= 3.0.3)
|
79
|
+
bundler (~> 1.0)
|
80
|
+
railties (= 3.0.3)
|
81
|
+
railties (3.0.3)
|
82
|
+
actionpack (= 3.0.3)
|
83
|
+
activesupport (= 3.0.3)
|
84
|
+
rake (>= 0.8.7)
|
85
|
+
thor (~> 0.14.4)
|
17
86
|
rake (0.8.7)
|
18
|
-
rspec (2.
|
19
|
-
rspec-core (~> 2.
|
20
|
-
rspec-expectations (~> 2.
|
21
|
-
rspec-mocks (~> 2.
|
22
|
-
rspec-core (2.
|
23
|
-
rspec-expectations (2.
|
87
|
+
rspec (2.3.0)
|
88
|
+
rspec-core (~> 2.3.0)
|
89
|
+
rspec-expectations (~> 2.3.0)
|
90
|
+
rspec-mocks (~> 2.3.0)
|
91
|
+
rspec-core (2.3.1)
|
92
|
+
rspec-expectations (2.3.0)
|
24
93
|
diff-lcs (~> 1.1.2)
|
25
|
-
rspec-mocks (2.
|
94
|
+
rspec-mocks (2.3.0)
|
95
|
+
rspec-rails (2.3.1)
|
96
|
+
actionpack (~> 3.0)
|
97
|
+
activesupport (~> 3.0)
|
98
|
+
railties (~> 3.0)
|
99
|
+
rspec (~> 2.3.0)
|
100
|
+
ruby2ruby (1.2.5)
|
101
|
+
ruby_parser (~> 2.0)
|
102
|
+
sexp_processor (~> 3.0)
|
103
|
+
ruby_parser (2.0.5)
|
104
|
+
sexp_processor (~> 3.0)
|
105
|
+
sexp_processor (3.0.5)
|
26
106
|
simple_uuid (0.1.1)
|
27
|
-
|
28
|
-
|
29
|
-
|
107
|
+
sourcify (0.4.0)
|
108
|
+
ruby2ruby (>= 1.2.5)
|
109
|
+
sexp_processor (>= 3.0.5)
|
110
|
+
spruz (0.2.2)
|
111
|
+
thor (0.14.6)
|
112
|
+
thrift (0.5.0)
|
113
|
+
thrift_client (0.6.0)
|
114
|
+
thrift (~> 0.5.0)
|
115
|
+
treetop (1.4.9)
|
116
|
+
polyglot (>= 0.3.1)
|
117
|
+
tzinfo (0.3.23)
|
118
|
+
wrong (0.5.0)
|
119
|
+
ParseTree (~> 3.0)
|
120
|
+
diff-lcs (~> 1.1.2)
|
121
|
+
file-tail (~> 1.0)
|
122
|
+
predicated (>= 0.2.2)
|
123
|
+
ruby2ruby (~> 1.2)
|
124
|
+
ruby_parser (~> 2.0.4)
|
125
|
+
sexp_processor (~> 3.0)
|
126
|
+
sourcify (>= 0.3.0)
|
127
|
+
yard (0.6.4)
|
30
128
|
|
31
129
|
PLATFORMS
|
32
130
|
ruby
|
33
131
|
|
34
132
|
DEPENDENCIES
|
35
133
|
active_column!
|
36
|
-
|
37
|
-
|
38
|
-
|
134
|
+
activesupport
|
135
|
+
bluecloth
|
136
|
+
cassandra (>= 0.9)
|
137
|
+
rails (>= 3.0)
|
138
|
+
rake
|
139
|
+
rspec-rails
|
140
|
+
wrong
|
141
|
+
yard
|
data/README.md
CHANGED
@@ -1,8 +1,21 @@
|
|
1
|
+
**IMPORTANT**: If you are reading this on the main ActiveColumn page on github, please go to
|
2
|
+
[the actual README page](./active_column/blob/master/README.md) so that links bring you to the right place.
|
3
|
+
|
1
4
|
# ActiveColumn
|
2
5
|
|
3
|
-
ActiveColumn is a framework for
|
4
|
-
|
5
|
-
|
6
|
+
ActiveColumn is a framework for working with data in Cassandra. It currently includes two features:
|
7
|
+
|
8
|
+
- Database migrations
|
9
|
+
- "Time line" model data management
|
10
|
+
|
11
|
+
Data migrations are very similar to those in ActiveRecord, and are documented in [Migrate](./docs/Migrate.md).
|
12
|
+
|
13
|
+
Time line data management is loosely based on concepts in ActiveRecord, but is adapted to saving data in which rows in
|
14
|
+
Cassandra grow indefinitely over time, such as in the oft-used Twitter example for Cassandra. This usage is documented
|
15
|
+
in:
|
16
|
+
|
17
|
+
- [Create](./docs/Create.md) - how to create data
|
18
|
+
- [Query](./docs/Query.md) - how to find data
|
6
19
|
|
7
20
|
## Installation
|
8
21
|
|
@@ -20,20 +33,15 @@ bundle install
|
|
20
33
|
|
21
34
|
### Configuration
|
22
35
|
|
23
|
-
ActiveColumn requires the [cassandra gem](https://github.com/fauna/cassandra)
|
24
|
-
|
25
|
-
|
26
|
-
<pre>
|
27
|
-
ActiveColumn.connection = Cassandra.new('my_keyspace', '127.0.0.1:9160')
|
28
|
-
</pre>
|
36
|
+
ActiveColumn requires Cassandra 0.7 or above, as we as the [cassandra gem](https://github.com/fauna/cassandra),
|
37
|
+
version 0.9 or above.
|
29
38
|
|
30
|
-
|
31
|
-
|
39
|
+
Data migrations in ActiveColumn are used within a Rails project, and are driven off of a configuration file,
|
40
|
+
config/cassandra.yml. It should look something like this:
|
32
41
|
|
33
|
-
|
42
|
+
_config/cassandra.yml_
|
34
43
|
<pre>
|
35
44
|
test:
|
36
|
-
home: ":"
|
37
45
|
servers: "127.0.0.1:9160"
|
38
46
|
keyspace: "myapp_test"
|
39
47
|
thrift:
|
@@ -41,7 +49,6 @@ test:
|
|
41
49
|
retries: 2
|
42
50
|
|
43
51
|
development:
|
44
|
-
home: ":"
|
45
52
|
servers: "127.0.0.1:9160"
|
46
53
|
keyspace: "myapp_development"
|
47
54
|
thrift:
|
@@ -49,7 +56,11 @@ development:
|
|
49
56
|
retries: 2
|
50
57
|
</pre>
|
51
58
|
|
52
|
-
|
59
|
+
In order to get time line modeling support, you must provide ActiveColumn with an instance of a Cassandra object.
|
60
|
+
Since you have your cassandra.yml from above, you can do this very simply like this:
|
61
|
+
|
62
|
+
|
63
|
+
_config/initializers/cassandra.rb_
|
53
64
|
<pre>
|
54
65
|
config = YAML.load_file(Rails.root.join("config", "cassandra.yml"))[Rails.env]
|
55
66
|
$cassandra = Cassandra.new(config['keyspace'],
|
@@ -63,131 +74,4 @@ As you can see, I create a global $cassandra variable, which I use in my tests t
|
|
63
74
|
|
64
75
|
One other thing to note is that you obviously must have Cassandra installed and running! Please take a look at the
|
65
76
|
[mama_cass gem](https://github.com/carbonfive/mama_cass) for a quick way to get up and running with Cassandra for
|
66
|
-
development and testing.
|
67
|
-
|
68
|
-
### Saving data
|
69
|
-
|
70
|
-
To make a model in to an ActiveColumn model, just extend ActiveColumn::Base, and provide two pieces of information:
|
71
|
-
|
72
|
-
- Column Family
|
73
|
-
- Function(s) to generate keys for your rows of data
|
74
|
-
|
75
|
-
The most basic form of using ActiveColumn looks like this:
|
76
|
-
<pre>
|
77
|
-
class Tweet < ActiveColumn::Base
|
78
|
-
column_family :tweets
|
79
|
-
key :user_id
|
80
|
-
end
|
81
|
-
</pre>
|
82
|
-
|
83
|
-
Then in your app you can create and save a tweet like this:
|
84
|
-
<pre>
|
85
|
-
tweet = Tweet.new( :user_id => 'mwynholds', :message => "I'm going for a bike ride" )
|
86
|
-
tweet.save
|
87
|
-
</pre>
|
88
|
-
|
89
|
-
When you run #save, ActiveColumn saves a new column in the "tweets" column family in the row with key "mwynholds". The
|
90
|
-
content of the row is the Tweet instance JSON-encoded.
|
91
|
-
|
92
|
-
*Key Generator Functions*
|
93
|
-
|
94
|
-
This is great, but quite often you want to save the content in multiple rows for the sake of speedy lookups. This is
|
95
|
-
basically de-normalizing data, and is extremely common in Cassandra data. ActiveColumn lets you do this quite easily
|
96
|
-
by telling it the name of a function to use to generate the keys during a save. It works like this:
|
97
|
-
|
98
|
-
<pre>
|
99
|
-
class Tweet < ActiveColumn::Base
|
100
|
-
column_family :tweets
|
101
|
-
key :user_id, :values => :generate_user_keys
|
102
|
-
|
103
|
-
def generate_user_keys
|
104
|
-
[ attributes[:user_id], 'all']
|
105
|
-
end
|
106
|
-
end
|
107
|
-
</pre>
|
108
|
-
|
109
|
-
The code to save the tweet is the same as the previous example, but now it saves the tweet in both the "mwynholds" row
|
110
|
-
and the "all" row. This way, you can pull out the last 20 of all tweets quite easily (assuming you needed to do this
|
111
|
-
in your app).
|
112
|
-
|
113
|
-
*Compound Keys*
|
114
|
-
|
115
|
-
In some cases you may want to have your rows keyed by multiple values. ActiveColumn supports compound keys,
|
116
|
-
and looks like this:
|
117
|
-
|
118
|
-
<pre>
|
119
|
-
class TweetDM < ActiveColumn::Base
|
120
|
-
column_family :tweet_dms
|
121
|
-
key :user_id, :values => :generate_user_keys
|
122
|
-
key :recipient_id, :values => :recipient_ids
|
123
|
-
|
124
|
-
def generate_user_keys
|
125
|
-
[ attributes[:user_id], 'all ]
|
126
|
-
end
|
127
|
-
end
|
128
|
-
</pre>
|
129
|
-
|
130
|
-
Now, when you create a new TweetDM, it might look like this:
|
131
|
-
|
132
|
-
<pre>
|
133
|
-
dm = TweetDM.new( :user_id => 'mwynholds', :recipient_ids => [ 'fsinatra', 'dmartin' ], :message => "Let's go to Vegas" )
|
134
|
-
</pre>
|
135
|
-
|
136
|
-
This tweet direct message will saved to four different rows in the "tweet_dms" column family, under these keys:
|
137
|
-
|
138
|
-
- mwynholds:fsinatra
|
139
|
-
- mwynholds:dmartin
|
140
|
-
- all:fsinatra
|
141
|
-
- all:dmartin
|
142
|
-
|
143
|
-
Now my app can pretty easily figure find all DMs I sent to Old Blue Eyes, or to Dino, and it can also easily find all
|
144
|
-
DMs sent from *anyone* to Frank or Dino.
|
145
|
-
|
146
|
-
One thing to note about the TweetDM class above is that the "keys" configuration at the top looks a little uglier than
|
147
|
-
before. If you have a compound key and any of the keys have custom key generators, you need to pass in an array of
|
148
|
-
single-element hashes. This is in place to support Ruby 1.8, which does not have ordered hashes. Making sure the keys
|
149
|
-
are ordered is necessary to keep the compounds keys canonical (ie: deterministic).
|
150
|
-
|
151
|
-
### Finding data
|
152
|
-
|
153
|
-
Ok, congratulations - now you have a bunch of fantastic data in Cassandra. How do you get it out? ActiveColumn can
|
154
|
-
help you here too.
|
155
|
-
|
156
|
-
Here is how you look up data that have a simple key:
|
157
|
-
|
158
|
-
<pre>
|
159
|
-
tweets = Tweet.find( 'mwynholds', :reversed => true, :count => 3 )
|
160
|
-
</pre>
|
161
|
-
|
162
|
-
This code will find the last 10 tweets for the 'mwynholds' user in reverse order. It comes back as a hash of arrays,
|
163
|
-
and would looks like this if represented in JSON:
|
164
|
-
|
165
|
-
<pre>
|
166
|
-
{
|
167
|
-
'mwynholds': [ { 'user_id': 'mwynholds', 'message': 'I\'m going to bed now' },
|
168
|
-
{ 'user_id': 'mwynholds', 'message': 'It\'s lunch time' },
|
169
|
-
{ 'user_id': 'mwynholds', 'message': 'Just woke up' } ]
|
170
|
-
}
|
171
|
-
</pre>
|
172
|
-
|
173
|
-
Here are some other examples and their return values:
|
174
|
-
|
175
|
-
<pre>
|
176
|
-
Tweet.find( [ 'mwynholds', 'all' ], :count => 2 )
|
177
|
-
|
178
|
-
{
|
179
|
-
'mwynholds': [ { 'user_id': 'mwynholds', 'message': 'Good morning' },
|
180
|
-
{ 'user_id': 'mwynholds', 'message': 'Good afternoon' } ],
|
181
|
-
'all': [ { 'user_id': 'mwynholds', 'message': 'Good morning' },
|
182
|
-
'user_id': 'bmurray', 'message': 'Who ya gonna call!' } ]
|
183
|
-
}
|
184
|
-
</pre>
|
185
|
-
|
186
|
-
<pre>
|
187
|
-
Tweet.find( { 'user_id' => 'all', 'recipient_id' => [ 'fsinatra', 'dmartin' ] }, :reversed => true, :count => 1 )
|
188
|
-
|
189
|
-
{
|
190
|
-
'all:fsinatra' => [ { 'user_id': 'mwynholds', 'recipient_ids' => [ 'fsinatra', 'dmartin' ], 'message' => 'Here we come Vegas!' } ],
|
191
|
-
'all:dmartin' => [ { 'user_id': 'fsinatra', 'recipient_ids' => [ 'dmartin' ], 'message' => 'Vegas was fun' } ]
|
192
|
-
}
|
193
|
-
</pre>
|
77
|
+
development and testing.
|
data/active_column.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.authors = ["Michael Wynholds"]
|
10
10
|
s.email = ["mike@wynholds.com"]
|
11
11
|
s.homepage = "https://github.com/carbonfive/active_column"
|
12
|
-
s.summary = %q{Provides time line support for Cassandra}
|
13
|
-
s.description = %q{Provides time line support for Cassandra}
|
12
|
+
s.summary = %q{Provides time line support and database migrations for Cassandra}
|
13
|
+
s.description = %q{Provides time line support and database migrations for Cassandra}
|
14
14
|
|
15
15
|
s.rubyforge_project = "active_column"
|
16
16
|
|
@@ -18,9 +18,15 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
|
+
s.has_rdoc = true
|
21
22
|
|
22
|
-
s.add_dependency '
|
23
|
-
s.add_dependency '
|
24
|
-
|
25
|
-
|
23
|
+
s.add_dependency 'cassandra', '>= 0.9'
|
24
|
+
s.add_dependency 'activesupport'
|
25
|
+
s.add_dependency 'rake'
|
26
|
+
|
27
|
+
s.add_development_dependency 'rails', '>= 3.0'
|
28
|
+
s.add_development_dependency 'rspec-rails'
|
29
|
+
s.add_development_dependency 'wrong'
|
30
|
+
s.add_development_dependency 'yard'
|
31
|
+
s.add_development_dependency 'bluecloth'
|
26
32
|
end
|
data/docs/Create.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
### Saving data
|
2
|
+
|
3
|
+
To make a model in to an ActiveColumn model, just extend ActiveColumn::Base, and provide two pieces of information:
|
4
|
+
|
5
|
+
- Column Family (optional)
|
6
|
+
- Function(s) to generate keys for your rows of data
|
7
|
+
|
8
|
+
If you do not specify a column family, it will default to the "tabelized" class name, just like ActiveRecord.
|
9
|
+
Example: Tweet --> tweets
|
10
|
+
Example: TweetDM --> tweet_dms
|
11
|
+
|
12
|
+
The most basic form of using ActiveColumn looks like this:
|
13
|
+
<pre>
|
14
|
+
class Tweet < ActiveColumn::Base
|
15
|
+
key :user_id
|
16
|
+
attr_accessor :user_id, :message
|
17
|
+
end
|
18
|
+
</pre>
|
19
|
+
|
20
|
+
Note that you can also use ActiveColumn as a mix-in, like this:
|
21
|
+
<pre>
|
22
|
+
class Tweet
|
23
|
+
include ActiveColumn
|
24
|
+
|
25
|
+
key :user_id
|
26
|
+
attr_accessor :user_id, :message
|
27
|
+
end
|
28
|
+
</pre>
|
29
|
+
|
30
|
+
Then in your app you can create and save a tweet like this:
|
31
|
+
<pre>
|
32
|
+
tweet = Tweet.new( :user_id => 'mwynholds', :message => "I'm going for a bike ride" )
|
33
|
+
tweet.save
|
34
|
+
</pre>
|
35
|
+
|
36
|
+
When you run #save, ActiveColumn saves a new column in the "tweets" column family in the row with key "mwynholds". The
|
37
|
+
content of the row is the Tweet instance JSON-encoded.
|
38
|
+
|
39
|
+
*Key Generator Functions*
|
40
|
+
|
41
|
+
This is great, but quite often you want to save the content in multiple rows for the sake of speedy lookups. This is
|
42
|
+
basically de-normalizing data, and is extremely common in Cassandra data. ActiveColumn lets you do this quite easily
|
43
|
+
by telling it the name of a function to use to generate the keys during a save. It works like this:
|
44
|
+
|
45
|
+
<pre>
|
46
|
+
class Tweet
|
47
|
+
include ActiveColumn
|
48
|
+
|
49
|
+
key :user_id, :values => :generate_user_keys
|
50
|
+
attr_accessor :user_id, :message
|
51
|
+
|
52
|
+
def generate_user_keys
|
53
|
+
[ user_id, 'all']
|
54
|
+
end
|
55
|
+
end
|
56
|
+
</pre>
|
57
|
+
|
58
|
+
The code to save the tweet is the same as the previous example, but now it saves the tweet in both the "mwynholds" row
|
59
|
+
and the "all" row. This way, you can pull out the last 20 of all tweets quite easily (assuming you needed to do this
|
60
|
+
in your app).
|
61
|
+
|
62
|
+
*Compound Keys*
|
63
|
+
|
64
|
+
In some cases you may want to have your rows keyed by multiple values. ActiveColumn supports compound keys,
|
65
|
+
and looks like this:
|
66
|
+
|
67
|
+
<pre>
|
68
|
+
class TweetDM
|
69
|
+
include ActiveColumn
|
70
|
+
|
71
|
+
column_family :tweet_dms
|
72
|
+
key :user_id, :values => :generate_user_keys
|
73
|
+
key :recipient_id, :values => :recipient_ids
|
74
|
+
attr_accessor :user_id, :recipient_ids, :message
|
75
|
+
|
76
|
+
def generate_user_keys
|
77
|
+
[ user_id, 'all ]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
</pre>
|
81
|
+
|
82
|
+
Now, when you create a new TweetDM, it might look like this:
|
83
|
+
|
84
|
+
<pre>
|
85
|
+
dm = TweetDM.new( :user_id => 'mwynholds', :recipient_ids => [ 'fsinatra', 'dmartin' ], :message => "Let's go to Vegas" )
|
86
|
+
</pre>
|
87
|
+
|
88
|
+
This tweet direct message will saved to four different rows in the "tweet_dms" column family, under these keys:
|
89
|
+
|
90
|
+
- mwynholds:fsinatra
|
91
|
+
- mwynholds:dmartin
|
92
|
+
- all:fsinatra
|
93
|
+
- all:dmartin
|
94
|
+
|
95
|
+
Now my app can pretty easily figure find all DMs I sent to Old Blue Eyes, or to Dino, and it can also easily find all
|
96
|
+
DMs sent from *anyone* to Frank or Dino.
|
97
|
+
|
98
|
+
One thing to note about the TweetDM class above is that the "keys" configuration at the top looks a little uglier than
|
99
|
+
before. If you have a compound key and any of the keys have custom key generators, you need to pass in an array of
|
100
|
+
single-element hashes. This is in place to support Ruby 1.8, which does not have ordered hashes. Making sure the keys
|
101
|
+
are ordered is necessary to keep the compounds keys canonical (ie: deterministic).
|