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