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 +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
|