sessionm-cassandra_object 2.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/.gitignore +2 -0
  2. data/CHANGELOG +3 -0
  3. data/Gemfile +2 -0
  4. data/LICENSE +13 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.markdown +12 -0
  7. data/Rakefile +15 -0
  8. data/lib/cassandra_object/associations/one_to_many.rb +146 -0
  9. data/lib/cassandra_object/associations/one_to_one.rb +85 -0
  10. data/lib/cassandra_object/associations.rb +50 -0
  11. data/lib/cassandra_object/attributes.rb +97 -0
  12. data/lib/cassandra_object/base.rb +97 -0
  13. data/lib/cassandra_object/batches.rb +31 -0
  14. data/lib/cassandra_object/callbacks.rb +27 -0
  15. data/lib/cassandra_object/collection.rb +8 -0
  16. data/lib/cassandra_object/connection.rb +29 -0
  17. data/lib/cassandra_object/consistency.rb +31 -0
  18. data/lib/cassandra_object/cursor.rb +90 -0
  19. data/lib/cassandra_object/dirty.rb +32 -0
  20. data/lib/cassandra_object/errors.rb +10 -0
  21. data/lib/cassandra_object/finder_methods.rb +72 -0
  22. data/lib/cassandra_object/generators/migration_generator.rb +31 -0
  23. data/lib/cassandra_object/generators/templates/migration.rb.erb +11 -0
  24. data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -0
  25. data/lib/cassandra_object/identity/custom_key_factory.rb +50 -0
  26. data/lib/cassandra_object/identity/hashed_natural_key_factory.rb +10 -0
  27. data/lib/cassandra_object/identity/key.rb +20 -0
  28. data/lib/cassandra_object/identity/natural_key_factory.rb +51 -0
  29. data/lib/cassandra_object/identity/uuid_key_factory.rb +39 -0
  30. data/lib/cassandra_object/identity.rb +52 -0
  31. data/lib/cassandra_object/log_subscriber.rb +37 -0
  32. data/lib/cassandra_object/migrations/migration.rb +15 -0
  33. data/lib/cassandra_object/migrations.rb +66 -0
  34. data/lib/cassandra_object/mocking.rb +15 -0
  35. data/lib/cassandra_object/persistence.rb +138 -0
  36. data/lib/cassandra_object/railtie.rb +11 -0
  37. data/lib/cassandra_object/schema/migration.rb +106 -0
  38. data/lib/cassandra_object/schema/migration_proxy.rb +25 -0
  39. data/lib/cassandra_object/schema/migrator.rb +213 -0
  40. data/lib/cassandra_object/schema.rb +37 -0
  41. data/lib/cassandra_object/serialization.rb +6 -0
  42. data/lib/cassandra_object/tasks/column_family.rb +90 -0
  43. data/lib/cassandra_object/tasks/keyspace.rb +89 -0
  44. data/lib/cassandra_object/tasks/ks.rake +121 -0
  45. data/lib/cassandra_object/timestamps.rb +19 -0
  46. data/lib/cassandra_object/type.rb +19 -0
  47. data/lib/cassandra_object/types/array_type.rb +16 -0
  48. data/lib/cassandra_object/types/boolean_type.rb +23 -0
  49. data/lib/cassandra_object/types/date_type.rb +20 -0
  50. data/lib/cassandra_object/types/float_type.rb +19 -0
  51. data/lib/cassandra_object/types/hash_type.rb +16 -0
  52. data/lib/cassandra_object/types/integer_type.rb +19 -0
  53. data/lib/cassandra_object/types/set_type.rb +22 -0
  54. data/lib/cassandra_object/types/string_type.rb +16 -0
  55. data/lib/cassandra_object/types/time_type.rb +27 -0
  56. data/lib/cassandra_object/types/time_with_zone_type.rb +18 -0
  57. data/lib/cassandra_object/types/utf8_string_type.rb +18 -0
  58. data/lib/cassandra_object/types.rb +11 -0
  59. data/lib/cassandra_object/validations.rb +46 -0
  60. data/lib/cassandra_object.rb +49 -0
  61. data/sessionm-cassandra_object.gemspec +26 -0
  62. data/test/active_model_test.rb +9 -0
  63. data/test/base_test.rb +28 -0
  64. data/test/batches_test.rb +30 -0
  65. data/test/connection_test.rb +28 -0
  66. data/test/consistency_test.rb +20 -0
  67. data/test/finder_methods_test.rb +49 -0
  68. data/test/identity_test.rb +30 -0
  69. data/test/persistence_test.rb +84 -0
  70. data/test/test_helper.rb +31 -0
  71. data/test/timestamps_test.rb +27 -0
  72. data/test/types/array_type_test.rb +15 -0
  73. data/test/types/boolean_type_test.rb +23 -0
  74. data/test/types/date_type_test.rb +4 -0
  75. data/test/types/float_type_test.rb +4 -0
  76. data/test/types/hash_type_test.rb +4 -0
  77. data/test/types/integer_type_test.rb +18 -0
  78. data/test/types/set_type_test.rb +17 -0
  79. data/test/types/string_type_test.rb +4 -0
  80. data/test/types/time_type_test.rb +4 -0
  81. data/test/types/utf8_string_type_test.rb +4 -0
  82. data/test/validations_test.rb +15 -0
  83. metadata +183 -0
@@ -0,0 +1,138 @@
1
+ module CassandraObject
2
+ module Persistence
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def remove(key)
7
+ ActiveSupport::Notifications.instrument("remove.cassandra_object", column_family: column_family, key: key) do
8
+ connection.remove(column_family, key.to_s, consistency: thrift_write_consistency)
9
+ end
10
+ end
11
+
12
+ def delete_all
13
+ ActiveSupport::Notifications.instrument("truncate.cassandra_object", column_family: column_family) do
14
+ connection.truncate!(column_family)
15
+ end
16
+ end
17
+
18
+ def create(attributes = {})
19
+ new(attributes).tap do |object|
20
+ object.save
21
+ end
22
+ end
23
+
24
+ def write(key, attributes, schema_version)
25
+ key.tap do |key|
26
+ attributes = encode_columns_hash(attributes, schema_version)
27
+ ActiveSupport::Notifications.instrument("insert.cassandra_object", column_family: column_family, key: key, attributes: attributes) do
28
+ connection.insert(column_family, key.to_s, attributes, consistency: thrift_write_consistency)
29
+ end
30
+ end
31
+ end
32
+
33
+ def instantiate(key, attributes)
34
+ # remove any attributes we don't know about. we would do this earlier, but we want to make such
35
+ # attributes available to migrations
36
+ attributes.delete_if { |k,_| model_attributes[k].nil? }
37
+
38
+ allocate.tap do |object|
39
+ object.instance_variable_set("@schema_version", attributes.delete('schema_version'))
40
+ object.instance_variable_set("@key", parse_key(key))
41
+ object.instance_variable_set("@new_record", false)
42
+ object.instance_variable_set("@destroyed", false)
43
+ object.instance_variable_set("@attributes", decode_columns_hash(attributes))
44
+ end
45
+ end
46
+
47
+ def encode_columns_hash(attributes, schema_version)
48
+ attributes.inject({}) do |memo, (column_name, value)|
49
+ # cassandra stores bytes, not strings, so it has no concept of encodings. The ruby thrift gem
50
+ # expects all strings to be encoded as ascii-8bit.
51
+ # don't attempt to encode columns that are nil
52
+ memo[column_name.to_s] = value.nil? ? '' : model_attributes[column_name].converter.encode(value).force_encoding('ASCII-8BIT')
53
+ memo
54
+ end.merge({"schema_version" => schema_version.to_s})
55
+ end
56
+
57
+ def decode_columns_hash(attributes)
58
+ Hash[attributes.map { |k, v| [k.to_s, model_attributes[k].converter.decode(v)] }]
59
+ end
60
+
61
+ def column_family_configuration
62
+ [{:Name => column_family, :CompareWith => "UTF8Type"}]
63
+ end
64
+ end
65
+
66
+ def new_record?
67
+ @new_record
68
+ end
69
+
70
+ def destroyed?
71
+ @destroyed
72
+ end
73
+
74
+ def persisted?
75
+ !(new_record? || destroyed?)
76
+ end
77
+
78
+ def save(*)
79
+ begin
80
+ create_or_update
81
+ rescue CassandraObject::RecordInvalid
82
+ false
83
+ end
84
+ end
85
+
86
+ def save!
87
+ create_or_update || raise(RecordNotSaved)
88
+ end
89
+
90
+ def destroy
91
+ self.class.remove(key)
92
+ @destroyed = true
93
+ freeze
94
+ end
95
+
96
+ def update_attribute(name, value)
97
+ name = name.to_s
98
+ send("#{name}=", value)
99
+ save(:validate => false)
100
+ end
101
+
102
+ def update_attributes(attributes)
103
+ self.attributes = attributes
104
+ save
105
+ end
106
+
107
+ def update_attributes!(attributes)
108
+ self.attributes = attributes
109
+ save!
110
+ end
111
+
112
+ def reload
113
+ @attributes.update(self.class.find(self.id).instance_variable_get('@attributes'))
114
+ end
115
+
116
+ private
117
+ def create_or_update
118
+ result = new_record? ? create : update
119
+ result != false
120
+ end
121
+
122
+ def create
123
+ @key ||= self.class.next_key(self)
124
+ write
125
+ @new_record = false
126
+ @key
127
+ end
128
+
129
+ def update
130
+ write
131
+ end
132
+
133
+ def write
134
+ changed_attributes = changed.inject({}) { |h, n| h[n] = read_attribute(n); h }
135
+ self.class.write(key, changed_attributes, schema_version)
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,11 @@
1
+ module CassandraObject
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load 'cassandra_object/tasks/ks.rake'
5
+ end
6
+
7
+ generators do
8
+ require 'cassandra_object/generators/migration_generator'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,106 @@
1
+ module CassandraObject
2
+ module Schema
3
+
4
+ class Migration
5
+
6
+ @@verbose = true
7
+ cattr_accessor :verbose
8
+
9
+ # Returns the raw connection to Cassandra
10
+ def self.connection
11
+ CassandraObject::Base.connection
12
+ end
13
+
14
+ def self.migrate(direction)
15
+ return unless respond_to?(direction)
16
+
17
+ case direction
18
+ when :up then announce "migrating"
19
+ when :down then announce "reverting"
20
+ end
21
+
22
+ result = nil
23
+ time = Benchmark.measure { result = send("#{direction}") }
24
+
25
+ case direction
26
+ when :up then announce "migrated (%.4fs)" % time.real; write
27
+ when :down then announce "reverted (%.4fs)" % time.real; write
28
+ end
29
+
30
+ result
31
+ end
32
+
33
+ # Creates a new column family with the given name. Column family configurations can be set within
34
+ # a block like this:
35
+ #
36
+ # create_column_family(:users) do |cf|
37
+ # cf.comment = 'Users column family'
38
+ # cf.comparator_type = 'TimeUUIDType'
39
+ # end
40
+ #
41
+ # A complete list of available configuration settings is here:
42
+ #
43
+ # http://github.com/fauna/cassandra/blob/master/vendor/0.7/gen-rb/cassandra_types.rb
44
+ #
45
+ # Scroll down to the CfDef definition.
46
+ def self.create_column_family(name, &block)
47
+ say_with_time("create_column_family #{name}") do
48
+ column_family_tasks.create(name, &block)
49
+ end
50
+ end
51
+
52
+ # Drops the given column family
53
+ def self.drop_column_family(name)
54
+ say_with_time("drop_column_family #{name}") do
55
+ column_family_tasks.drop(name)
56
+ end
57
+ end
58
+
59
+ # Renames the column family from the old name to the new name
60
+ def self.rename_column_family(old_name, new_name)
61
+ say_with_time("rename_column_family #{name}") do
62
+ column_family_tasks.rename(old_name, new_name)
63
+ end
64
+ end
65
+
66
+ def self.write(text="")
67
+ puts(text) if verbose
68
+ end
69
+
70
+ def self.announce(message)
71
+ version = defined?(@version) ? @version : nil
72
+
73
+ text = "#{version} #{name}: #{message}"
74
+ length = [0, 75 - text.length].max
75
+ write "== %s %s" % [text, "=" * length]
76
+ end
77
+
78
+ def self.say(message, subitem=false)
79
+ write "#{subitem ? " ->" : "--"} #{message}"
80
+ end
81
+
82
+ def self.say_with_time(message)
83
+ say(message)
84
+ result = nil
85
+ time = Benchmark.measure { result = yield }
86
+ say "%.4fs" % time.real, :subitem
87
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
88
+ result
89
+ end
90
+
91
+ def self.suppress_messages
92
+ save, self.verbose = verbose, false
93
+ yield
94
+ ensure
95
+ self.verbose = save
96
+ end
97
+
98
+ private
99
+
100
+ def self.column_family_tasks
101
+ Tasks::ColumnFamily.new(CassandraObject::Base.connection.keyspace)
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,25 @@
1
+ module CassandraObject
2
+ module Schema
3
+
4
+ # MigrationProxy is used to defer loading of the actual migration classes
5
+ # until they are needed
6
+ class MigrationProxy
7
+
8
+ attr_accessor :name, :version, :filename
9
+
10
+ delegate :migrate, :announce, :write, :to=>:migration
11
+
12
+ private
13
+
14
+ def migration
15
+ @migration ||= load_migration
16
+ end
17
+
18
+ def load_migration
19
+ require(File.expand_path(filename))
20
+ name.constantize
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,213 @@
1
+ module CassandraObject
2
+ module Schema
3
+
4
+ class Migrator
5
+
6
+ def self.migrate(migrations_path, target_version = nil)
7
+ case
8
+ when target_version.nil?
9
+ up(migrations_path, target_version)
10
+ when current_version == 0 && target_version == 0
11
+ when current_version > target_version
12
+ down(migrations_path, target_version)
13
+ else
14
+ up(migrations_path, target_version)
15
+ end
16
+ end
17
+
18
+ def self.rollback(migrations_path, steps = 1)
19
+ move(:down, migrations_path, steps)
20
+ end
21
+
22
+ def self.forward(migrations_path, steps = 1)
23
+ move(:up, migrations_path, steps)
24
+ end
25
+
26
+ def self.up(migrations_path, target_version = nil)
27
+ new(:up, migrations_path, target_version).migrate
28
+ end
29
+
30
+ def self.down(migrations_path, target_version = nil)
31
+ new(:down, migrations_path, target_version).migrate
32
+ end
33
+
34
+ def self.run(direction, migrations_path, target_version)
35
+ new(direction, migrations_path, target_version).run
36
+ end
37
+
38
+ def self.migrations_path
39
+ 'ks/migrate'
40
+ end
41
+
42
+ def self.schema_migrations_column_family
43
+ :schema_migrations
44
+ end
45
+
46
+ def self.column_family_tasks
47
+ cas = CassandraObject::Base.connection
48
+ Tasks::ColumnFamily.new(cas.keyspace)
49
+ end
50
+
51
+ def self.get_all_versions
52
+ cas = CassandraObject::Base.connection
53
+ cas.get(schema_migrations_column_family, 'all').map {|(name, _value)| name.to_i}.sort
54
+ end
55
+
56
+ def self.current_version
57
+ sm_cf = schema_migrations_column_family
58
+ if column_family_tasks.exists?(sm_cf)
59
+ get_all_versions.max || 0
60
+ else
61
+ 0
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def self.move(direction, migrations_path, steps)
68
+ migrator = self.new(direction, migrations_path)
69
+ start_index = migrator.migrations.index(migrator.current_migration)
70
+
71
+ if start_index
72
+ finish = migrator.migrations[start_index + steps]
73
+ version = finish ? finish.version : 0
74
+ send(direction, migrations_path, version)
75
+ end
76
+ end
77
+
78
+ public
79
+
80
+ def initialize(direction, migrations_path, target_version = nil)
81
+ sm_cf = self.class.schema_migrations_column_family
82
+
83
+ unless column_family_tasks.exists?(sm_cf)
84
+ column_family_tasks.create(sm_cf) do |cf|
85
+ cf.comparator_type = 'LongType'
86
+ end
87
+ end
88
+
89
+ @direction, @migrations_path, @target_version = direction, migrations_path, target_version
90
+ end
91
+
92
+ def current_version
93
+ migrated.last || 0
94
+ end
95
+
96
+ def current_migration
97
+ migrations.detect { |m| m.version == current_version }
98
+ end
99
+
100
+ def run
101
+ target = migrations.detect { |m| m.version == @target_version }
102
+ raise UnknownMigrationVersionError.new(@target_version) if target.nil?
103
+ unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
104
+ target.migrate(@direction)
105
+ record_version_state_after_migrating(target)
106
+ end
107
+ end
108
+
109
+ def migrate
110
+ current = migrations.detect { |m| m.version == current_version }
111
+ target = migrations.detect { |m| m.version == @target_version }
112
+
113
+ if target.nil? && !@target_version.nil? && @target_version > 0
114
+ raise UnknownMigrationVersionError.new(@target_version)
115
+ end
116
+
117
+ start = up? ? 0 : (migrations.index(current) || 0)
118
+ finish = migrations.index(target) || migrations.size - 1
119
+ runnable = migrations[start..finish]
120
+
121
+ # skip the last migration if we're headed down, but not ALL the way down
122
+ runnable.pop if down? && !target.nil?
123
+
124
+ runnable.each do |migration|
125
+ #puts "Migrating to #{migration.name} (#{migration.version})"
126
+
127
+ # On our way up, we skip migrating the ones we've already migrated
128
+ next if up? && migrated.include?(migration.version.to_i)
129
+
130
+ # On our way down, we skip reverting the ones we've never migrated
131
+ if down? && !migrated.include?(migration.version.to_i)
132
+ migration.announce 'never migrated, skipping'; migration.write
133
+ next
134
+ end
135
+
136
+ migration.migrate(@direction)
137
+ record_version_state_after_migrating(migration)
138
+ end
139
+ end
140
+
141
+ def migrations
142
+ @migrations ||= begin
143
+ files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
144
+
145
+ migrations = files.inject([]) do |klasses, file|
146
+ version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
147
+
148
+ raise IllegalMigrationNameError.new(file) unless version
149
+ version = version.to_i
150
+
151
+ if klasses.detect { |m| m.version == version }
152
+ raise DuplicateMigrationVersionError.new(version)
153
+ end
154
+
155
+ if klasses.detect { |m| m.name == name.camelize }
156
+ raise DuplicateMigrationNameError.new(name.camelize)
157
+ end
158
+
159
+ migration = MigrationProxy.new
160
+ migration.name = name.camelize
161
+ migration.version = version
162
+ migration.filename = file
163
+ klasses << migration
164
+ end
165
+
166
+ migrations = migrations.sort_by { |m| m.version }
167
+ down? ? migrations.reverse : migrations
168
+ end
169
+ end
170
+
171
+ def pending_migrations
172
+ already_migrated = migrated
173
+ migrations.reject { |m| already_migrated.include?(m.version.to_i) }
174
+ end
175
+
176
+ def migrated
177
+ @migrated_versions ||= self.class.get_all_versions
178
+ end
179
+
180
+ private
181
+
182
+ def column_family_tasks
183
+ Tasks::ColumnFamily.new(connection.keyspace)
184
+ end
185
+
186
+ def connection
187
+ CassandraObject::Base.connection
188
+ end
189
+
190
+ def record_version_state_after_migrating(migration)
191
+ sm_cf = self.class.schema_migrations_column_family
192
+
193
+ @migrated_versions ||= []
194
+ if down?
195
+ @migrated_versions.delete(migration.version)
196
+ connection.remove sm_cf, 'all', migration.version
197
+ else
198
+ @migrated_versions.push(migration.version).sort!
199
+ connection.insert sm_cf, 'all', { migration.version => migration.name }
200
+ end
201
+ end
202
+
203
+ def up?
204
+ @direction == :up
205
+ end
206
+
207
+ def down?
208
+ @direction == :down
209
+ end
210
+
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,37 @@
1
+ module CassandraObject
2
+ module Schema
3
+ extend ActiveSupport::Autoload
4
+
5
+ class IrreversibleMigration < StandardError
6
+ end
7
+
8
+ class DuplicateMigrationVersionError < StandardError#:nodoc:
9
+ def initialize(version)
10
+ super("Multiple migrations have the version number #{version}")
11
+ end
12
+ end
13
+
14
+ class DuplicateMigrationNameError < StandardError#:nodoc:
15
+ def initialize(name)
16
+ super("Multiple migrations have the name #{name}")
17
+ end
18
+ end
19
+
20
+ class UnknownMigrationVersionError < StandardError #:nodoc:
21
+ def initialize(version)
22
+ super("No migration with version number #{version}")
23
+ end
24
+ end
25
+
26
+ class IllegalMigrationNameError < StandardError#:nodoc:
27
+ def initialize(name)
28
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
29
+ end
30
+ end
31
+
32
+ autoload :Migrator
33
+ autoload :Migration
34
+ autoload :MigrationProxy
35
+
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ module CassandraObject
2
+ module Serialization
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Serializers::JSON
5
+ end
6
+ end
@@ -0,0 +1,90 @@
1
+ module CassandraObject
2
+
3
+ module Tasks
4
+
5
+ class ColumnFamily
6
+
7
+ COMPARATOR_TYPES = { :time => 'TimeUUIDType',
8
+ :timestamp => 'TimeUUIDType',
9
+ :long => 'LongType',
10
+ :string => 'BytesType',
11
+ :utf8 => 'UTF8Type' }
12
+
13
+ COLUMN_TYPES = { :standard => 'Standard',
14
+ :super => 'Super' }
15
+
16
+ def initialize(keyspace)
17
+ @keyspace = keyspace
18
+ end
19
+
20
+ def exists?(name)
21
+ connection.schema.cf_defs.find { |cf_def| cf_def.name == name.to_s }
22
+ end
23
+
24
+ def create(name, &block)
25
+ cf = Cassandra::ColumnFamily.new
26
+ cf.name = name.to_s
27
+ cf.keyspace = @keyspace.to_s
28
+ cf.comparator_type = 'BytesType'
29
+ cf.column_type = 'Standard'
30
+
31
+ block.call cf if block
32
+
33
+ post_process_column_family(cf)
34
+ connection.add_column_family(cf)
35
+ end
36
+
37
+ def drop(name)
38
+ connection.drop_column_family(name.to_s)
39
+ end
40
+
41
+ def rename(old_name, new_name)
42
+ connection.rename_column_family(old_name.to_s, new_name.to_s)
43
+ end
44
+
45
+ def clear(name)
46
+ connection.truncate!(name.to_s)
47
+ end
48
+
49
+ private
50
+
51
+ def connection
52
+ CassandraObject::Base.connection
53
+ end
54
+
55
+ def post_process_column_family(cf)
56
+ comp_type = cf.comparator_type
57
+ if comp_type && COMPARATOR_TYPES.has_key?(comp_type)
58
+ cf.comparator_type = COMPARATOR_TYPES[comp_type]
59
+ end
60
+
61
+ comp_type = cf.subcomparator_type
62
+ if comp_type && COMPARATOR_TYPES.has_key?(comp_type)
63
+ cf.subcomparator_type = COMPARATOR_TYPES[comp_type]
64
+ end
65
+
66
+ col_type = cf.column_type
67
+ if col_type && COLUMN_TYPES.has_key?(col_type)
68
+ cf.column_type = COLUMN_TYPES[col_type]
69
+ end
70
+
71
+ cf
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+
80
+ class Cassandra
81
+ class ColumnFamily
82
+ def with_fields(options)
83
+ struct_fields.collect { |f| f[1][:name] }.each do |f|
84
+ send("#{f}=", options[f.to_sym] || options[f.to_s])
85
+ end
86
+ self
87
+ end
88
+ end
89
+ end
90
+