massive_record 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/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # v0.2.0 (git develop)
2
+
3
+
4
+ # v0.1.2 (git master)
5
+
6
+
7
+
8
+
9
+
10
+ # v0.1.1
11
+
12
+ - A ORM record now now if it is read only or not.
13
+ - Added a logger to ORM::Base, which is set to Rails.logger if gem is used with Rails.
14
+ - The database cleaner will no longer destroy tables, only delete all of their contents between tests. Speed tests up a lot.
15
+ - known_attribute_names are now available as instance method as well.
16
+ - If you add a created_at attribute it will be maintained by the ORM with the time the object was created.
17
+ - An adapter (wrapper) row now has an updated_at attribute. ORM objects also responds to updated_at
18
+ - Bugfix: Database cleaner no longer tries to remove tables with same name twice.
19
+
20
+
21
+
22
+ # v0.1.0
23
+
24
+ - Communication with Hbase via Thrift.
25
+ - Basic ORM capabilities.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- massive_record (0.1.0)
4
+ massive_record (0.1.1)
5
5
  activemodel
6
6
  activesupport
7
7
  thrift (>= 0.5.0)
@@ -17,22 +17,19 @@ GEM
17
17
  builder (2.1.2)
18
18
  diff-lcs (1.1.2)
19
19
  i18n (0.5.0)
20
- rspec (2.2.0)
21
- rspec-core (~> 2.2)
22
- rspec-expectations (~> 2.2)
23
- rspec-mocks (~> 2.2)
24
- rspec-core (2.2.1)
25
- rspec-expectations (2.2.0)
20
+ rspec (2.4.0)
21
+ rspec-core (~> 2.4.0)
22
+ rspec-expectations (~> 2.4.0)
23
+ rspec-mocks (~> 2.4.0)
24
+ rspec-core (2.4.0)
25
+ rspec-expectations (2.4.0)
26
26
  diff-lcs (~> 1.1.2)
27
- rspec-mocks (2.2.0)
27
+ rspec-mocks (2.4.0)
28
28
  thrift (0.5.0)
29
29
 
30
30
  PLATFORMS
31
31
  ruby
32
32
 
33
33
  DEPENDENCIES
34
- activemodel
35
- activesupport
36
34
  massive_record!
37
35
  rspec (>= 2.1.0)
38
- thrift (>= 0.5.0)
data/README.md CHANGED
@@ -53,6 +53,7 @@ Both MassiveRecord::ORM::Table and MassiveRecord::ORM::Column do now have some f
53
53
  - Information about changes on attributes.
54
54
  - Casting of attributes
55
55
  - Serialization of array / hashes
56
+ - Timestamps like created_at and updated_at. Updated at will always be available, created_at must be defined. See example down:
56
57
 
57
58
  Tables also have:
58
59
  - Persistencey method calls like create, save and destroy (but they do not actually save things to hbase)
@@ -74,6 +75,8 @@ Here is an example of usage, both for Table and Column:
74
75
  field :points, :integer, :default => 0
75
76
  field :date_of_birth, :date
76
77
  field :newsletter, :boolean, :default => false
78
+
79
+ timestamps # ..or field :created_at, :time
77
80
  end
78
81
 
79
82
  validates_presence_of :name, :email
@@ -145,7 +148,6 @@ You can, if you'd like, work directly against the adapter.
145
148
  ## Planned work
146
149
 
147
150
  - Rename Wrapper to Adapter, and make it easy to switch from Thrift to another way of communicating with Hbase.
148
- - Automatically handling time stamps like created_at and updated_at.
149
151
  - Associations and embedded objects.
150
152
  - Implement other Adapters, for instance using jruby and the Java API.
151
153
 
@@ -15,4 +15,8 @@ require 'massive_record/thrift/hbase'
15
15
  require 'massive_record/wrapper/base'
16
16
 
17
17
  # ORM
18
- require 'massive_record/orm/base'
18
+ require 'massive_record/orm/base'
19
+
20
+ if defined?(::Rails) && ::Rails::VERSION::MAJOR == 3
21
+ require 'massive_record/rails/railtie'
22
+ end
@@ -16,6 +16,7 @@ require 'massive_record/orm/attribute_methods/read'
16
16
  require 'massive_record/orm/attribute_methods/dirty'
17
17
  require 'massive_record/orm/validations'
18
18
  require 'massive_record/orm/callbacks'
19
+ require 'massive_record/orm/timestamps'
19
20
  require 'massive_record/orm/persistence'
20
21
 
21
22
  module MassiveRecord
@@ -23,6 +24,9 @@ module MassiveRecord
23
24
  class Base
24
25
  include ActiveModel::Conversion
25
26
 
27
+ # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
28
+ cattr_accessor :logger, :instance_writer => false
29
+
26
30
  # Add a prefix or a suffix to the table name
27
31
  # example:
28
32
  #
@@ -63,7 +67,7 @@ module MassiveRecord
63
67
  self.attributes_raw = attributes_from_field_definition.merge(attributes)
64
68
  self.attributes = attributes
65
69
  @new_record = true
66
- @destroyed = false
70
+ @destroyed = @readonly = false
67
71
 
68
72
  _run_initialize_callbacks
69
73
  end
@@ -83,7 +87,7 @@ module MassiveRecord
83
87
  # person.name # => 'Alice'
84
88
  def init_with(coder)
85
89
  @new_record = false
86
- @destroyed = false
90
+ @destroyed = @readonly = false
87
91
 
88
92
  self.attributes_raw = coder['attributes']
89
93
 
@@ -107,9 +111,8 @@ module MassiveRecord
107
111
  end
108
112
 
109
113
 
110
-
111
114
  def inspect
112
- attributes_as_string = self.class.known_attribute_names.collect do |attr_name|
115
+ attributes_as_string = known_attribute_names_for_inspect.collect do |attr_name|
113
116
  "#{attr_name}: #{attribute_for_inspect(attr_name)}"
114
117
  end.join(', ')
115
118
 
@@ -117,18 +120,6 @@ module MassiveRecord
117
120
  end
118
121
 
119
122
 
120
- def attribute_for_inspect(attr_name)
121
- value = read_attribute(attr_name)
122
-
123
- if value.is_a?(String) && value.length > 50
124
- "#{value[0..50]}...".inspect
125
- elsif value.is_a?(Date) || value.is_a?(Time)
126
- %("#{value.to_s}")
127
- else
128
- value.inspect
129
- end
130
- end
131
-
132
123
  def id
133
124
  if read_attribute(:id).blank? && respond_to?(:default_id, true)
134
125
  @attributes["id"] = default_id
@@ -139,10 +130,41 @@ module MassiveRecord
139
130
 
140
131
 
141
132
 
133
+ def readonly?
134
+ !!@readonly
135
+ end
136
+
137
+ def readonly!
138
+ @readonly = true
139
+ end
142
140
 
143
141
 
144
142
  private
145
143
 
144
+ #
145
+ # A place to hook in if you need to add attributes
146
+ # not known by the attribute schema in the inspect string.
147
+ # Remember to include a call to super in your module so you
148
+ # don't break the chain if you override it.
149
+ # See Timestamps for an example
150
+ #
151
+ def known_attribute_names_for_inspect
152
+ (self.class.known_attribute_names + (super rescue [])).uniq
153
+ end
154
+
155
+ def attribute_for_inspect(attr_name)
156
+ value = read_attribute(attr_name)
157
+
158
+ if value.is_a?(String) && value.length > 50
159
+ "#{value[0..50]}...".inspect
160
+ elsif value.is_a?(Date) || value.is_a?(Time)
161
+ %("#{value.to_s}")
162
+ else
163
+ value.inspect
164
+ end
165
+ end
166
+
167
+
146
168
  def next_id
147
169
  IdFactory.next_for(self.class).to_s
148
170
  end
@@ -159,6 +181,7 @@ module MassiveRecord
159
181
  include AttributeMethods::Dirty
160
182
  include Validations
161
183
  include Callbacks
184
+ include Timestamps
162
185
 
163
186
 
164
187
  alias [] read_attribute
@@ -16,7 +16,7 @@ module MassiveRecord
16
16
  if @@connection.blank?
17
17
  @@connection = if !connection_configuration.empty?
18
18
  MassiveRecord::Wrapper::Connection.new(connection_configuration)
19
- elsif defined? Rails
19
+ elsif defined? ::Rails
20
20
  MassiveRecord::Wrapper::Base.connection
21
21
  else
22
22
  raise ConnectionConfigurationMissing
@@ -43,5 +43,14 @@ module MassiveRecord
43
43
  # requiring a number to work on.
44
44
  class NotNumericalFieldError < MassiveRecordError
45
45
  end
46
+
47
+ # Raised if you try to assign a variable which can't be set manually,
48
+ # for instance time stamps
49
+ class CantBeManuallyAssigned < MassiveRecordError
50
+ end
51
+
52
+ # Raised if you try to save a record which is read only
53
+ class ReadOnlyRecord < MassiveRecordError
54
+ end
46
55
  end
47
56
  end
@@ -111,6 +111,7 @@ module MassiveRecord
111
111
 
112
112
 
113
113
  def create_or_update
114
+ raise ReadOnlyRecord if readonly?
114
115
  !!(new_record? ? create : update)
115
116
  end
116
117
 
@@ -85,6 +85,10 @@ module MassiveRecord
85
85
  self << Field.new_with_arguments_from_dsl(*args)
86
86
  end
87
87
 
88
+ # Internal DSL method
89
+ def timestamps
90
+ field :created_at, :time
91
+ end
88
92
 
89
93
  # Internal DSL method
90
94
  def autoload_fields
@@ -29,6 +29,11 @@ module MassiveRecord
29
29
  fields << Field.new_with_arguments_from_dsl(*args)
30
30
  end
31
31
 
32
+
33
+ def timestamps
34
+ add_field :created_at, :time
35
+ end
36
+
32
37
  #
33
38
  # If you need to add fields dynamically, use this method.
34
39
  # It wraps functionality needed to keep the class in a consistent state.
@@ -42,6 +42,10 @@ module MassiveRecord
42
42
  def attributes_schema
43
43
  self.class.attributes_schema
44
44
  end
45
+
46
+ def known_attribute_names
47
+ self.class.known_attribute_names
48
+ end
45
49
  end
46
50
  end
47
51
  end
@@ -0,0 +1,69 @@
1
+ module MassiveRecord
2
+ module ORM
3
+ module Timestamps
4
+ extend ActiveSupport::Concern
5
+
6
+
7
+ included do
8
+ before_create :if => :set_created_at_on_create? do
9
+ raise "created_at must be of type time" if attributes_schema['created_at'].type != :time
10
+ @attributes['created_at'] = Time.now
11
+ end
12
+ end
13
+
14
+
15
+
16
+
17
+ module ClassMethods
18
+ private
19
+
20
+ def transpose_hbase_columns_to_record_attributes(row)
21
+ attributes = super
22
+ attributes['updated_at'] = row.updated_at
23
+ attributes
24
+ end
25
+ end
26
+
27
+
28
+
29
+
30
+ def updated_at
31
+ self['updated_at']
32
+ end
33
+
34
+ def write_attribute(attr_name, value)
35
+ attr_name = attr_name.to_s
36
+
37
+ if attr_name == 'updated_at' || (attr_name == 'created_at' && has_created_at?)
38
+ raise MassiveRecord::ORM::CantBeManuallyAssigned.new("#{attr_name} can't be manually assigned.")
39
+ end
40
+
41
+ super
42
+ end
43
+
44
+
45
+
46
+ private
47
+
48
+ def update(*)
49
+ # Not 100% accurat, as we might should re-read the saved row from
50
+ # the database to fetch exactly the correct updated at time, but
51
+ # it should do for now as it takes an extra query to check the time stamp.
52
+ @attributes['updated_at'] = Time.now if updated = super
53
+ updated
54
+ end
55
+
56
+ def set_created_at_on_create?
57
+ has_created_at?
58
+ end
59
+
60
+ def has_created_at?
61
+ known_attribute_names.include? 'created_at'
62
+ end
63
+
64
+ def known_attribute_names_for_inspect
65
+ out = (super rescue []) << 'updated_at'
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,9 @@
1
+ module MassiveRecord
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie
4
+ initializer "massive_record.logger" do
5
+ MassiveRecord::ORM::Base.logger = ::Rails.logger
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,7 @@
1
1
  require 'active_support/secure_random'
2
2
 
3
3
  #
4
- # So, this module does a couple of things:
4
+ # This module does a couple of things:
5
5
  # 1. Iterates over all tables and adds a prefix to
6
6
  # them so that the classes will be uniq for
7
7
  # the test run.
@@ -14,39 +14,35 @@ module MassiveRecord
14
14
  extend ActiveSupport::Concern
15
15
 
16
16
  included do
17
- before(:all) { add_prefix_to_tables }
17
+ before(:all) { add_suffix_to_tables }
18
18
  after(:each) { delete_all_tables }
19
19
  end
20
20
 
21
-
22
-
23
-
24
21
  private
25
22
 
23
+ def add_suffix_to_tables
24
+ each_orm_class do |klass|
25
+ table_name_overriden = klass.table_name_overriden
26
+ klass.reset_table_name_configuration!
26
27
 
27
-
28
-
29
- def add_prefix_to_tables
30
- prefix = ActiveSupport::SecureRandom.hex(3)
31
- each_orm_class { |klass| klass.table_name_prefix = ["test", prefix, klass.table_name_prefix].reject(&:blank?).join("_") + "_" }
28
+ klass.table_name = table_name_overriden
29
+ klass.table_name_suffix = '_test'
30
+ end
32
31
  end
33
32
 
34
33
  def delete_all_tables
35
- each_orm_class_where_table_exists { |klass| klass.table.destroy }
34
+ tables = MassiveRecord::ORM::Base.connection.tables
35
+ each_orm_class do |klass|
36
+ if tables.include? klass.table.name
37
+ klass.table.all.each(&:destroy) # Don't want to use ORM, as it triggers callbacks etc..
38
+ tables.delete(klass.table.name)
39
+ end
40
+ end
36
41
  end
37
42
 
38
-
39
-
40
-
41
-
42
-
43
43
  def each_orm_class
44
44
  MassiveRecord::ORM::Table.descendants.each { |klass| yield klass }
45
45
  end
46
-
47
- def each_orm_class_where_table_exists
48
- MassiveRecord::ORM::Table.descendants.select { |klass| klass.connection.tables.include? klass.table_name }.each { |klass| yield klass }
49
- end
50
46
  end
51
47
  end
52
48
  end
@@ -1,3 +1,3 @@
1
1
  module MassiveRecord
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -13,7 +13,7 @@ module MassiveRecord
13
13
  class Base
14
14
 
15
15
  def self.config
16
- config = YAML.load_file(Rails.root.join('config', 'hbase.yml'))[Rails.env]
16
+ config = YAML.load_file(::Rails.root.join('config', 'hbase.yml'))[::Rails.env]
17
17
  { :host => config['host'], :port => config['port'] }
18
18
  end
19
19
 
@@ -25,4 +25,4 @@ module MassiveRecord
25
25
 
26
26
  end
27
27
  end
28
- end
28
+ end
@@ -41,7 +41,7 @@ module MassiveRecord
41
41
  def values
42
42
  @columns.inject({"id" => id}) {|h, (column_name, cell)| h[column_name] = cell.deserialize_value; h}
43
43
  end
44
-
44
+
45
45
  def values=(data)
46
46
  @values = {}
47
47
  update_columns(data)
@@ -165,6 +165,9 @@ module MassiveRecord
165
165
  self
166
166
  end
167
167
 
168
+ def updated_at
169
+ columns.values.collect(&:created_at).max
170
+ end
168
171
  end
169
172
  end
170
173
  end
@@ -3,7 +3,7 @@ module MassiveRecord
3
3
  class Scanner
4
4
 
5
5
  attr_accessor :connection, :table_name, :column_family_names, :opened_scanner
6
- attr_accessor :start_key, :created_at, :limit
6
+ attr_accessor :start_key, :offset_key, :created_at, :limit
7
7
  attr_accessor :formatted_column_family_names, :column_family_names
8
8
 
9
9
  def initialize(connection, table_name, column_family_names, opts = {})
@@ -13,15 +13,20 @@ module MassiveRecord
13
13
  @column_family_names = opts[:columns] unless opts[:columns].nil?
14
14
  @formatted_column_family_names = column_family_names.collect{|n| "#{n.split(":").first}:"}
15
15
  @start_key = opts[:start_key].to_s
16
+ @offset_key = opts[:offset_key].to_s
16
17
  @created_at = opts[:created_at].to_s
17
18
  @limit = opts[:limit] || 10
18
19
  end
19
20
 
21
+ def key
22
+ start_key.empty? ? offset_key : start_key
23
+ end
24
+
20
25
  def open
21
26
  if created_at.empty?
22
- self.opened_scanner = connection.scannerOpen(table_name, start_key, formatted_column_family_names)
27
+ self.opened_scanner = connection.scannerOpen(table_name, key, formatted_column_family_names)
23
28
  else
24
- self.opened_scanner = connection.scannerOpenTs(table_name, start_key, formatted_column_family_names, created_at)
29
+ self.opened_scanner = connection.scannerOpenTs(table_name, key, formatted_column_family_names, created_at)
25
30
  end
26
31
  end
27
32
 
@@ -38,7 +43,13 @@ module MassiveRecord
38
43
  end
39
44
 
40
45
  def populate_rows(results)
41
- results.collect{|result| populate_row(result)}
46
+ results.collect do |result|
47
+ if offset_key.empty?
48
+ populate_row(result) unless result.row.match(/^#{start_key}/).nil?
49
+ else
50
+ populate_row(result)
51
+ end
52
+ end.select{|r| !r.nil?}
42
53
  end
43
54
 
44
55
  def populate_row(result)
@@ -86,7 +86,8 @@ module MassiveRecord
86
86
 
87
87
  def format_options_for_scanner(opts = {})
88
88
  {
89
- :start_key => opts[:start],
89
+ :start_key => opts[:start],
90
+ :offset_key => opts[:offset],
90
91
  :created_at => opts[:created_at],
91
92
  :columns => opts[:select], # list of column families to fetch from hbase
92
93
  :limit => opts[:limit] || opts[:batch_size]
@@ -173,4 +173,27 @@ describe MassiveRecord::ORM::Base do
173
173
  @test_object.foo.should == "new_value"
174
174
  end
175
175
  end
176
+
177
+
178
+ describe "logger" do
179
+ it "should respond to logger" do
180
+ MassiveRecord::ORM::Base.should respond_to :logger
181
+ end
182
+
183
+ it "should respond to logger=" do
184
+ MassiveRecord::ORM::Base.should respond_to :logger=
185
+ end
186
+ end
187
+
188
+ describe "read only" do
189
+ it "should not be read only by default" do
190
+ TestClass.new.should_not be_readonly
191
+ end
192
+
193
+ it "should be read only if asked to" do
194
+ test = TestClass.new
195
+ test.readonly!
196
+ test.should be_readonly
197
+ end
198
+ end
176
199
  end
@@ -51,7 +51,7 @@ describe "configuration" do
51
51
  describe "under Rails" do
52
52
  before do
53
53
  TestClass.connection_configuration = {}
54
- module Rails; end
54
+ module ::Rails; end
55
55
  MassiveRecord::Wrapper::Base.stub!(:connection).and_return(@mock_connection)
56
56
  end
57
57
 
@@ -476,4 +476,22 @@ describe "persistence" do
476
476
  end
477
477
  end
478
478
  end
479
+
480
+ describe "read only objects" do
481
+ include MockMassiveRecordConnection
482
+
483
+ it "should raise an error if new record is read only and you try to save it" do
484
+ person = Person.new :id => "id1", :name => "Thorbjorn", :age => 29
485
+ person.readonly!
486
+ lambda { person.save }.should raise_error MassiveRecord::ORM::ReadOnlyRecord
487
+ end
488
+
489
+ it "should raise an error if record is read only and you try to save it" do
490
+ person = Person.create :id => "id1", :name => "Thorbjorn", :age => 29
491
+ person.should be_persisted
492
+
493
+ person.readonly!
494
+ lambda { person.save }.should raise_error MassiveRecord::ORM::ReadOnlyRecord
495
+ end
496
+ end
479
497
  end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/person'
3
+ require 'orm/models/person_with_timestamps'
4
+
5
+ describe "timestamps" do
6
+ include CreatePersonBeforeEach
7
+
8
+ before do
9
+ @person = Person.first
10
+ end
11
+
12
+
13
+ describe "#updated_at" do
14
+ it "should have updated at equal to nil on new records" do
15
+ Person.new.updated_at.should be_nil
16
+ end
17
+
18
+ it "should not be possible to set updated at" do
19
+ lambda { @person.updated_at = Time.now }.should raise_error MassiveRecord::ORM::CantBeManuallyAssigned
20
+ lambda { @person['updated_at'] = Time.now }.should raise_error MassiveRecord::ORM::CantBeManuallyAssigned
21
+ lambda { @person.write_attribute(:updated_at, Time.now) }.should raise_error MassiveRecord::ORM::CantBeManuallyAssigned
22
+ end
23
+
24
+ it "should have updated at on a persisted record" do
25
+ @person.updated_at.should be_a_kind_of Time
26
+ end
27
+
28
+ it "should be included in the list of known_attribute_names_for_inspect" do
29
+ @person.send(:known_attribute_names_for_inspect).should include 'updated_at'
30
+ end
31
+
32
+ it "should include updated_at in inspect" do
33
+ @person.inspect.should include(%q{updated_at:})
34
+ end
35
+
36
+ it "should be updated after a save" do
37
+ sleep(1)
38
+
39
+ updated_at_was = @person.updated_at
40
+ @person.update_attribute :name, "Should Give Me New Updated At"
41
+
42
+ @person.updated_at.should_not == updated_at_was
43
+ end
44
+
45
+ it "should not be updated after a save which failed" do
46
+ sleep(1)
47
+
48
+ updated_at_was = @person.updated_at
49
+ @person.name = nil
50
+
51
+ @person.should_not be_valid
52
+
53
+ @person.updated_at.should == updated_at_was
54
+ end
55
+ end
56
+
57
+ describe "#created_at" do
58
+ before do
59
+ @person_with_timestamps = PersonWithTimestamps.create! :name => "John Doe", :email => "john@base.com", :age => "20"
60
+ end
61
+
62
+ it "should have created at" do
63
+ @person_with_timestamps.should be_set_created_at_on_create
64
+ end
65
+
66
+ it "should not have created at on create if model does not have created at" do
67
+ @person.should_not be_set_created_at_on_create
68
+ end
69
+
70
+ it "should have updated at equal to nil on new records" do
71
+ PersonWithTimestamps.new.created_at.should be_nil
72
+ end
73
+
74
+ it "should not be possible to set updated at" do
75
+ lambda { @person_with_timestamps.created_at = Time.now }.should raise_error MassiveRecord::ORM::CantBeManuallyAssigned
76
+ lambda { @person_with_timestamps['created_at'] = Time.now }.should raise_error MassiveRecord::ORM::CantBeManuallyAssigned
77
+ lambda { @person_with_timestamps.write_attribute(:created_at, Time.now) }.should raise_error MassiveRecord::ORM::CantBeManuallyAssigned
78
+ end
79
+
80
+ it "should not raise cant-set-error if object has no timestamps" do
81
+ lambda { @person.created_at = Time.now }.should_not raise_error MassiveRecord::ORM::CantBeManuallyAssigned
82
+ end
83
+
84
+ it "should have created_at on a persisted record" do
85
+ @person_with_timestamps.created_at.should be_a_kind_of Time
86
+ end
87
+
88
+ it "should not alter created at on update" do
89
+ created_at_was = @person_with_timestamps.created_at
90
+
91
+ sleep(1)
92
+
93
+ @person_with_timestamps.update_attribute :name, @person_with_timestamps.name + "NEW"
94
+ @person_with_timestamps.created_at.should == created_at_was
95
+ end
96
+
97
+ it "should be included in the list of known_attribute_names_for_inspect" do
98
+ @person_with_timestamps.send(:known_attribute_names_for_inspect).should include 'created_at'
99
+ end
100
+
101
+ it "should include created_at in inspect" do
102
+ @person_with_timestamps.inspect.should include(%q{created_at:})
103
+ end
104
+
105
+ it "should not include created_at if object does not have it" do
106
+ @person.send(:known_attribute_names_for_inspect).should_not include 'created_at'
107
+ end
108
+
109
+ it "should raise error if created_at is not time" do
110
+ PersonWithTimestamps.attributes_schema['created_at'].type = :string
111
+
112
+ lambda { PersonWithTimestamps.create! }.should raise_error "created_at must be of type time"
113
+
114
+ PersonWithTimestamps.attributes_schema['created_at'].type = :time
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,19 @@
1
+ class PersonWithTimestamps < MassiveRecord::ORM::Table
2
+ column_family :info do
3
+ field :name
4
+ field :email
5
+ field :age, :integer
6
+ field :points, :integer, :default => 1, :column => :pts
7
+ field :date_of_birth, :date
8
+ field :status, :boolean, :default => false
9
+ field :addresses, :hash, :default => {}
10
+
11
+ field :created_at, :time
12
+ end
13
+
14
+ private
15
+
16
+ def default_id
17
+ next_id
18
+ end
19
+ end
@@ -64,6 +64,15 @@ describe MassiveRecord::ORM::Schema::TableInterface do
64
64
  TestColumnInterface.known_attribute_names.should include("name", "age")
65
65
  end
66
66
 
67
+ it "should make known_attribute_names readable for instances" do
68
+ class TestColumnInterface
69
+ field :name, :string
70
+ end
71
+
72
+ TestColumnInterface.new.known_attribute_names.should include('name')
73
+ end
74
+
75
+
67
76
  it "should give us default attributes from schema" do
68
77
  class TestColumnInterface
69
78
  field :name
@@ -75,6 +84,18 @@ describe MassiveRecord::ORM::Schema::TableInterface do
75
84
  defaults["age"].should == 1
76
85
  end
77
86
 
87
+ describe "timestamps" do
88
+ before do
89
+ class TestColumnInterface
90
+ timestamps
91
+ end
92
+ end
93
+
94
+ it "should have a created_at time field" do
95
+ TestColumnInterface.attributes_schema['created_at'].type.should == :time
96
+ end
97
+ end
98
+
78
99
 
79
100
  describe "dynamically adding a field" do
80
101
  it "should be possible to dynamically add a field" do
@@ -87,6 +87,16 @@ describe MassiveRecord::ORM::Schema::TableInterface do
87
87
  TestInterface.new.attributes_schema["name"].type.should == :string
88
88
  end
89
89
 
90
+ it "should make known_attribute_names readable for instances" do
91
+ class TestInterface
92
+ column_family :info do
93
+ field :name
94
+ end
95
+ end
96
+
97
+ TestInterface.new.known_attribute_names.should include('name')
98
+ end
99
+
90
100
  it "should not be shared amonb subclasses" do
91
101
  class TestInterface
92
102
  column_family :info do
@@ -98,6 +108,20 @@ describe MassiveRecord::ORM::Schema::TableInterface do
98
108
  TestInterfaceSubClass.column_families.should be_nil
99
109
  end
100
110
 
111
+ describe "timestamps" do
112
+ before do
113
+ class TestInterface
114
+ column_family :info do
115
+ timestamps
116
+ end
117
+ end
118
+ end
119
+
120
+ it "should have a created_at time field" do
121
+ TestInterface.attributes_schema['created_at'].type.should == :time
122
+ end
123
+ end
124
+
101
125
 
102
126
  describe "dynamically adding a field" do
103
127
  it "should be possible to dynamically add a field" do
@@ -83,7 +83,7 @@ describe MassiveRecord::Wrapper::Table do
83
83
  @table.all(:limit => 1, :select => ["info"]).first.column_families.should == ["info"]
84
84
  @table.find("ID1", :select => ["info"]).column_families.should == ["info"]
85
85
  end
86
-
86
+
87
87
  it "should update row values" do
88
88
  row = @table.first
89
89
  row.values["info:first_name"].should eql("John")
@@ -100,6 +100,46 @@ describe MassiveRecord::Wrapper::Table do
100
100
  row.update_columns({ :info => { :first_name => "Bob" } })
101
101
  row.save.should be_true
102
102
  end
103
+
104
+ it "should have a updated_at for a row" do
105
+ row = @table.first
106
+ row.updated_at.should be_a_kind_of Time
107
+ end
108
+
109
+ it "should have an updated_at for the row which is taken from the last updated attribute" do
110
+ sleep(1)
111
+ row = @table.first
112
+ row.update_columns({ :info => { :first_name => "New Bob" } })
113
+ row.save
114
+
115
+ sleep(1)
116
+
117
+ row = @table.first
118
+
119
+ row.columns["info:first_name"].created_at.should == row.updated_at
120
+ end
121
+
122
+ it "should have a new updated at for a row" do
123
+ row = @table.first
124
+ updated_at_was = row.updated_at
125
+
126
+ sleep(1)
127
+
128
+ row.update_columns({ :info => { :first_name => "Bob" } })
129
+ row.save
130
+
131
+ row = @table.first
132
+
133
+ updated_at_was.should_not == row.updated_at
134
+ end
135
+
136
+ it "should return nil if no cells has been created" do
137
+ row = MassiveRecord::Wrapper::Row.new
138
+ row.updated_at.should be_nil
139
+ end
140
+
141
+
142
+
103
143
 
104
144
  it "should merge data" do
105
145
  row = @table.first
@@ -185,7 +225,7 @@ describe MassiveRecord::Wrapper::Table do
185
225
 
186
226
  @table.all.size.should == 5
187
227
  end
188
-
228
+
189
229
  it "should find rows" do
190
230
  ids_list = [["ID1"], ["ID1", "ID2", "ID3"]]
191
231
  ids_list.each do |ids|
@@ -194,26 +234,39 @@ describe MassiveRecord::Wrapper::Table do
194
234
  end
195
235
  end
196
236
  end
197
-
237
+
198
238
  it "should collect 5 IDs" do
199
239
  @table.all.collect(&:id).should eql(1.upto(5).collect{|i| "ID#{i}"})
200
240
  end
201
-
241
+
202
242
  it "should iterate through a collection of rows" do
203
243
  @table.all.each do |row|
204
244
  row.id.should_not be_nil
205
245
  end
206
246
  end
207
-
247
+
208
248
  it "should iterate through a collection of rows using a batch process" do
209
249
  group_number = 0
210
- @table.find_in_batches(:batch_size => 2, :start => "ID2", :select => ["info"]) do |group|
250
+ @table.find_in_batches(:batch_size => 2, :select => ["info"]) do |group|
211
251
  group_number += 1
212
252
  group.each do |row|
213
253
  row.id.should_not be_nil
214
254
  end
215
255
  end
216
- group_number.should == 2
256
+ group_number.should == 3
257
+ end
258
+
259
+ it "should find 1 row using the :start option" do
260
+ @table.all(:start => "ID1").size.should == 1
261
+ end
262
+
263
+ it "should find 5 rows using the :start option" do
264
+ @table.all(:start => "ID").size.should == 5
265
+
266
+ end
267
+
268
+ it "should find 4 rows using the :offset option" do
269
+ @table.all(:offset => "ID2").size.should == 4
217
270
  end
218
271
 
219
272
  it "should exists in the database" do
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
  - Companybook
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-01-20 00:00:00 +01:00
17
+ date: 2011-02-04 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -85,6 +85,7 @@ files:
85
85
  - .autotest
86
86
  - .gitignore
87
87
  - .rspec
88
+ - CHANGELOG.md
88
89
  - Gemfile
89
90
  - Gemfile.lock
90
91
  - Manifest
@@ -115,7 +116,9 @@ files:
115
116
  - lib/massive_record/orm/schema/fields.rb
116
117
  - lib/massive_record/orm/schema/table_interface.rb
117
118
  - lib/massive_record/orm/table.rb
119
+ - lib/massive_record/orm/timestamps.rb
118
120
  - lib/massive_record/orm/validations.rb
121
+ - lib/massive_record/rails/railtie.rb
119
122
  - lib/massive_record/spec/support/simple_database_cleaner.rb
120
123
  - lib/massive_record/thrift/hbase.rb
121
124
  - lib/massive_record/thrift/hbase_constants.rb
@@ -146,9 +149,11 @@ files:
146
149
  - spec/orm/cases/id_factory_spec.rb
147
150
  - spec/orm/cases/persistence_spec.rb
148
151
  - spec/orm/cases/table_spec.rb
152
+ - spec/orm/cases/timestamps_spec.rb
149
153
  - spec/orm/cases/validation_spec.rb
150
154
  - spec/orm/models/address.rb
151
155
  - spec/orm/models/person.rb
156
+ - spec/orm/models/person_with_timestamps.rb
152
157
  - spec/orm/models/test_class.rb
153
158
  - spec/orm/schema/column_families_spec.rb
154
159
  - spec/orm/schema/column_family_spec.rb
@@ -210,9 +215,11 @@ test_files:
210
215
  - spec/orm/cases/id_factory_spec.rb
211
216
  - spec/orm/cases/persistence_spec.rb
212
217
  - spec/orm/cases/table_spec.rb
218
+ - spec/orm/cases/timestamps_spec.rb
213
219
  - spec/orm/cases/validation_spec.rb
214
220
  - spec/orm/models/address.rb
215
221
  - spec/orm/models/person.rb
222
+ - spec/orm/models/person_with_timestamps.rb
216
223
  - spec/orm/models/test_class.rb
217
224
  - spec/orm/schema/column_families_spec.rb
218
225
  - spec/orm/schema/column_family_spec.rb