massive_record 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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