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 +25 -0
- data/Gemfile.lock +8 -11
- data/README.md +3 -1
- data/lib/massive_record.rb +5 -1
- data/lib/massive_record/orm/base.rb +39 -16
- data/lib/massive_record/orm/config.rb +1 -1
- data/lib/massive_record/orm/errors.rb +9 -0
- data/lib/massive_record/orm/persistence.rb +1 -0
- data/lib/massive_record/orm/schema/column_family.rb +4 -0
- data/lib/massive_record/orm/schema/column_interface.rb +5 -0
- data/lib/massive_record/orm/schema/common_interface.rb +4 -0
- data/lib/massive_record/orm/timestamps.rb +69 -0
- data/lib/massive_record/rails/railtie.rb +9 -0
- data/lib/massive_record/spec/support/simple_database_cleaner.rb +16 -20
- data/lib/massive_record/version.rb +1 -1
- data/lib/massive_record/wrapper/base.rb +2 -2
- data/lib/massive_record/wrapper/row.rb +4 -1
- data/lib/massive_record/wrapper/scanner.rb +15 -4
- data/lib/massive_record/wrapper/table.rb +2 -1
- data/spec/orm/cases/base_spec.rb +23 -0
- data/spec/orm/cases/config_spec.rb +1 -1
- data/spec/orm/cases/persistence_spec.rb +18 -0
- data/spec/orm/cases/timestamps_spec.rb +117 -0
- data/spec/orm/models/person_with_timestamps.rb +19 -0
- data/spec/orm/schema/column_interface_spec.rb +21 -0
- data/spec/orm/schema/table_interface_spec.rb +24 -0
- data/spec/wrapper/cases/table_spec.rb +60 -7
- metadata +10 -3
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.
|
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.
|
21
|
-
rspec-core (~> 2.
|
22
|
-
rspec-expectations (~> 2.
|
23
|
-
rspec-mocks (~> 2.
|
24
|
-
rspec-core (2.
|
25
|
-
rspec-expectations (2.
|
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.
|
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
|
|
data/lib/massive_record.rb
CHANGED
@@ -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 =
|
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
|
@@ -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.
|
@@ -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
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'active_support/secure_random'
|
2
2
|
|
3
3
|
#
|
4
|
-
#
|
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) {
|
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
|
-
|
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
|
-
|
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
|
@@ -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,
|
27
|
+
self.opened_scanner = connection.scannerOpen(table_name, key, formatted_column_family_names)
|
23
28
|
else
|
24
|
-
self.opened_scanner = connection.scannerOpenTs(table_name,
|
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
|
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]
|
data/spec/orm/cases/base_spec.rb
CHANGED
@@ -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
|
@@ -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, :
|
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 ==
|
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
|
-
-
|
9
|
-
version: 0.1.
|
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-
|
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
|