active_column 0.0.2 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  pkg/*
2
+ .yardoc/*
2
3
  *.gem
3
4
  .bundle
4
5
  .idea
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.1)
5
- simple_uuid
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
- cassandra (0.8.2)
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.4.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.2.0)
19
- rspec-core (~> 2.2)
20
- rspec-expectations (~> 2.2)
21
- rspec-mocks (~> 2.2)
22
- rspec-core (2.2.1)
23
- rspec-expectations (2.2.0)
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.2.0)
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
- thrift (0.2.0.4)
28
- thrift_client (0.5.0)
29
- thrift (~> 0.2.0)
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
- cassandra
37
- rspec
38
- simple_uuid
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 saving and retrieving data from Cassandra in a "time line" model. It is loosely based
4
- on concepts in ActiveRecord, but is adapted to saving data in which rows in Cassandra grow indefinitely over time, such
5
- as in the oft-used Twitter example for Cassandra.
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). You must provide ActiveColumn with an
24
- instance of a Cassandra object. You can do this very simply like this:
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
- However, in a real app this is not flexible enough, so I often create a cassandra.yml file and configure Cassandra in an
31
- initializer.
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
- config/cassandra.yml
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
- config/initializers/cassandra.rb
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 &lt; 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 &lt; 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 &lt; 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.
@@ -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 'simple_uuid'
23
- s.add_dependency 'cassandra'
24
-
25
- s.add_development_dependency 'rspec'
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 &lt; 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).