active_type 0.1.0

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +16 -0
  5. data/LICENSE +22 -0
  6. data/README.md +149 -0
  7. data/Rakefile +37 -0
  8. data/active_type.gemspec +24 -0
  9. data/gemfiles/Gemfile.3.2 +7 -0
  10. data/gemfiles/Gemfile.3.2.lock +44 -0
  11. data/gemfiles/Gemfile.4.0 +7 -0
  12. data/gemfiles/Gemfile.4.0.lock +52 -0
  13. data/gemfiles/Gemfile.4.1 +7 -0
  14. data/gemfiles/Gemfile.4.1.lock +51 -0
  15. data/lib/active_type/extended_record/inheritance.rb +41 -0
  16. data/lib/active_type/extended_record.rb +27 -0
  17. data/lib/active_type/no_table.rb +65 -0
  18. data/lib/active_type/object.rb +13 -0
  19. data/lib/active_type/record.rb +15 -0
  20. data/lib/active_type/version.rb +3 -0
  21. data/lib/active_type/virtual_attributes.rb +166 -0
  22. data/lib/active_type.rb +6 -0
  23. data/spec/active_type/extended_record/single_table_inheritance_spec.rb +62 -0
  24. data/spec/active_type/extended_record_spec.rb +118 -0
  25. data/spec/active_type/object_spec.rb +256 -0
  26. data/spec/active_type/record_spec.rb +173 -0
  27. data/spec/integration/sign_in_spec.rb +98 -0
  28. data/spec/integration/sign_up_spec.rb +90 -0
  29. data/spec/shared_examples/accessors.rb +24 -0
  30. data/spec/shared_examples/belongs_to.rb +17 -0
  31. data/spec/shared_examples/coercible_columns.rb +203 -0
  32. data/spec/shared_examples/constructor.rb +30 -0
  33. data/spec/shared_examples/mass_assignment.rb +26 -0
  34. data/spec/spec_helper.rb +24 -0
  35. data/spec/support/database.rb +31 -0
  36. data/spec/support/error_on.rb +12 -0
  37. data/spec/support/i18n.rb +1 -0
  38. data/spec/support/protected_params.rb +20 -0
  39. data/spec/support/time_zone.rb +1 -0
  40. metadata +141 -0
@@ -0,0 +1,166 @@
1
+ module ActiveType
2
+
3
+ class InvalidAttributeNameError < StandardError; end
4
+ class MissingAttributeError < StandardError; end
5
+
6
+ module VirtualAttributes
7
+
8
+ class VirtualColumn < ActiveRecord::ConnectionAdapters::Column
9
+
10
+ def initialize(name, type)
11
+ @name = name
12
+ @type = type
13
+ end
14
+
15
+ def type_cast(value)
16
+ case @type
17
+ when :integer
18
+ case value
19
+ when FalseClass
20
+ 0
21
+ when TrueClass
22
+ 1
23
+ when "", nil
24
+ nil
25
+ else
26
+ value.to_i
27
+ end
28
+ when :timestamp, :datetime
29
+ if ActiveRecord::Base.time_zone_aware_attributes
30
+ time = super
31
+ if time
32
+ ActiveSupport::TimeWithZone.new(nil, Time.zone, time)
33
+ else
34
+ time
35
+ end
36
+ else
37
+ super
38
+ end
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ class AccessorGenerator
47
+
48
+ def initialize(mod)
49
+ @module = mod
50
+ end
51
+
52
+ def generate_accessors(name)
53
+ validate_attribute_name!(name)
54
+ generate_reader(name)
55
+ generate_writer(name)
56
+ end
57
+
58
+ private
59
+
60
+ def generate_reader(name)
61
+ @module.module_eval <<-BODY, __FILE__, __LINE__ + 1
62
+ def #{name}
63
+ read_virtual_attribute('#{name}')
64
+ end
65
+ BODY
66
+ end
67
+
68
+ def generate_writer(name)
69
+ @module.module_eval <<-BODY, __FILE__, __LINE__ + 1
70
+ def #{name}=(value)
71
+ write_virtual_attribute('#{name}', value)
72
+ end
73
+ BODY
74
+ end
75
+
76
+ def validate_attribute_name!(name)
77
+ unless name.to_s =~ /\A[A-z0-9_]*\z/
78
+ raise InvalidAttributeNameError.new("'#{name}' is not a valid name for a virtual attribute")
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+
85
+ extend ActiveSupport::Concern
86
+
87
+ included do
88
+ class_attribute :virtual_columns_hash
89
+ self.virtual_columns_hash = {}
90
+ end
91
+
92
+ def initialize(*)
93
+ @virtual_attributes = {}
94
+ @virtual_attributes_cache = {}
95
+ super
96
+ end
97
+
98
+ def [](name)
99
+ if self.singleton_class._has_virtual_column?(name)
100
+ read_virtual_attribute(name)
101
+ else
102
+ super
103
+ end
104
+ end
105
+
106
+ def []=(name, value)
107
+ if self.singleton_class._has_virtual_column?(name)
108
+ write_virtual_attribute(name, value)
109
+ else
110
+ super
111
+ end
112
+ end
113
+
114
+ def read_virtual_attribute(name)
115
+ name = name.to_s
116
+ @virtual_attributes_cache[name] ||= begin
117
+ self.singleton_class._virtual_column(name).type_cast(@virtual_attributes[name])
118
+ end
119
+ end
120
+
121
+ def write_virtual_attribute(name, value)
122
+ name = name.to_s
123
+ @virtual_attributes_cache.delete(name)
124
+ @virtual_attributes[name] = value
125
+ end
126
+
127
+ module ClassMethods
128
+
129
+ def _virtual_column(name)
130
+ virtual_columns_hash[name.to_s] || begin
131
+ if defined?(super)
132
+ super
133
+ else
134
+ raise MissingAttributeError.new("Undefined attribute '#{name}'")
135
+ end
136
+ end
137
+ end
138
+
139
+ def _has_virtual_column?(name)
140
+ virtual_columns_hash.has_key?(name.to_s) || begin
141
+ if defined?(super)
142
+ super
143
+ else
144
+ false
145
+ end
146
+ end
147
+ end
148
+
149
+ def generated_virtual_attribute_methods
150
+ @generated_virtual_attribute_methods ||= begin
151
+ mod = Module.new
152
+ include mod
153
+ mod
154
+ end
155
+ end
156
+
157
+ def attribute(name, type)
158
+ self.virtual_columns_hash = virtual_columns_hash.merge(name.to_s => VirtualColumn.new(name, type))
159
+ AccessorGenerator.new(generated_virtual_attribute_methods).generate_accessors(name)
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+
166
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ require 'active_type/version'
4
+
5
+ require 'active_type/record'
6
+ require 'active_type/object'
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ module STISpec
4
+
5
+ class Parent < ActiveRecord::Base
6
+ self.table_name = 'sti_records'
7
+ end
8
+
9
+ class Child < Parent
10
+ end
11
+
12
+ class ExtendedChild < ActiveType::Record[Child]
13
+ end
14
+
15
+ class ExtendedExtendedChild < ActiveType::Record[ExtendedChild]
16
+ end
17
+
18
+ end
19
+
20
+
21
+ describe 'ActiveType::Record[STIModel]' do
22
+
23
+ describe 'persistence' do
24
+
25
+ def should_save_and_load(save_as, load_as)
26
+ record = save_as.new(:persisted_string => "string")
27
+ record.save.should be_true
28
+
29
+ reloaded_child = load_as.find(record.id)
30
+ reloaded_child.persisted_string.should == "string"
31
+ reloaded_child.should be_a(load_as)
32
+ end
33
+
34
+ it 'can save and load the active type record' do
35
+
36
+ should_save_and_load(STISpec::ExtendedChild, STISpec::ExtendedChild)
37
+ end
38
+
39
+ it 'can save as base and load as active type record' do
40
+ should_save_and_load(STISpec::Child, STISpec::ExtendedChild)
41
+ end
42
+
43
+ it 'can save as active type and load as base record' do
44
+ should_save_and_load(STISpec::ExtendedChild, STISpec::Child)
45
+ end
46
+
47
+ it 'can load via the base class and convert to active type record' do
48
+ record = STISpec::ExtendedChild.new(:persisted_string => "string")
49
+ record.save.should be_true
50
+
51
+ reloaded_child = STISpec::Child.find(record.id).becomes(STISpec::ExtendedChild)
52
+ reloaded_child.persisted_string.should == "string"
53
+ reloaded_child.should be_a(STISpec::ExtendedChild)
54
+ end
55
+
56
+ it 'can save classes further down the inheritance tree' do
57
+ should_save_and_load(STISpec::ExtendedExtendedChild, STISpec::ExtendedExtendedChild)
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ module ExtendedRecordSpec
4
+
5
+ class BaseRecord < ActiveRecord::Base
6
+ self.table_name = 'records'
7
+ end
8
+
9
+ class BaseActiveTypeRecord < ActiveType::Record
10
+ self.table_name = 'records'
11
+
12
+ attribute :virtual_string, :string
13
+ end
14
+
15
+ class ExtendedRecord < ActiveType::Record[BaseRecord]
16
+ attribute :another_virtual_string, :string
17
+ end
18
+
19
+ class ExtendedActiveTypeRecord < ActiveType::Record[BaseActiveTypeRecord]
20
+ attribute :another_virtual_string, :string
21
+ end
22
+
23
+
24
+ class ExtendedRecordWithValidations < ExtendedActiveTypeRecord
25
+ validates :persisted_string, :presence => true
26
+ validates :virtual_string, :presence => true
27
+ validates :another_virtual_string, :presence => true
28
+ end
29
+
30
+ end
31
+
32
+
33
+ describe "ActiveType::Record[ActiveRecord::Base]" do
34
+
35
+ subject { ExtendedRecordSpec::ExtendedRecord.new }
36
+
37
+ it 'is inherits from the base type' do
38
+ subject.should be_a(ExtendedRecordSpec::BaseRecord)
39
+ end
40
+
41
+ describe 'constructors' do
42
+ subject { ExtendedRecordSpec::ExtendedRecord }
43
+
44
+ it_should_behave_like 'ActiveRecord-like constructors', { :persisted_string => "persisted string", :another_virtual_string => "another virtual string" }
45
+ end
46
+
47
+ describe 'accessors' do
48
+ it_should_behave_like 'ActiveRecord-like accessors', { :persisted_string => "persisted string", :another_virtual_string => "another virtual string" }
49
+ end
50
+
51
+ describe 'persistence' do
52
+ it 'persists to the database' do
53
+ subject.persisted_string = "persisted string"
54
+ subject.save.should be_true
55
+
56
+ subject.class.find(subject.id).persisted_string.should == "persisted string"
57
+ end
58
+ end
59
+
60
+ describe '.find' do
61
+ it 'returns an instance of the extended model' do
62
+ subject.save
63
+
64
+ subject.class.find(subject.id).should be_a(subject.class)
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+
71
+ describe "ActiveType::Record[ActiveType::Record]" do
72
+
73
+ subject { ExtendedRecordSpec::ExtendedActiveTypeRecord.new }
74
+
75
+ it 'is inherits from the base type' do
76
+ subject.should be_a(ExtendedRecordSpec::BaseActiveTypeRecord)
77
+ end
78
+
79
+ it 'has the same model name as the base class' do
80
+ subject.class.model_name.singular.should == ExtendedRecordSpec::BaseActiveTypeRecord.model_name.singular
81
+ end
82
+
83
+ describe 'constructors' do
84
+ subject { ExtendedRecordSpec::ExtendedActiveTypeRecord }
85
+
86
+ it_should_behave_like 'ActiveRecord-like constructors', { :persisted_string => "persisted string", :virtual_string => "virtual string", :another_virtual_string => "another virtual string" }
87
+ end
88
+
89
+ describe 'accessors' do
90
+ it_should_behave_like 'ActiveRecord-like accessors', { :persisted_string => "persisted string", :virtual_string => "virtual string", :another_virtual_string => "another virtual string" }
91
+ end
92
+
93
+ describe 'validations' do
94
+ subject { ExtendedRecordSpec::ExtendedRecordWithValidations.new }
95
+
96
+ it { should have(1).error_on(:persisted_string) }
97
+ it { should have(1).error_on(:virtual_string) }
98
+ it { should have(1).error_on(:another_virtual_string) }
99
+ end
100
+
101
+ describe 'persistence' do
102
+ it 'persists to the database' do
103
+ subject.persisted_string = "persisted string"
104
+ subject.save.should be_true
105
+
106
+ subject.class.find(subject.id).persisted_string.should == "persisted string"
107
+ end
108
+ end
109
+
110
+ describe '.find' do
111
+ it 'returns an instance of the extended model' do
112
+ subject.save
113
+
114
+ subject.class.find(subject.id).should be_a(subject.class)
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,256 @@
1
+ require 'spec_helper'
2
+
3
+ module ObjectSpec
4
+
5
+ class Object < ActiveType::Object
6
+
7
+ attribute :virtual_string, :string
8
+ attribute :virtual_integer, :integer
9
+ attribute :virtual_time, :datetime
10
+ attribute :virtual_date, :date
11
+ attribute :virtual_boolean, :boolean
12
+
13
+ end
14
+
15
+
16
+ class ObjectWithValidations < Object
17
+
18
+ validates :virtual_string, :presence => true
19
+
20
+ end
21
+
22
+
23
+ class ObjectWithOverrides < Object
24
+
25
+ attribute :overridable_test, :string
26
+
27
+ def overridable_test
28
+ super + super
29
+ end
30
+
31
+ end
32
+
33
+
34
+ class InheritingObject < Object
35
+ attribute :another_virtual_string, :string
36
+ end
37
+
38
+
39
+ class IncludingObject < Object
40
+
41
+ module Module
42
+ extend ActiveSupport::Concern
43
+
44
+ included do
45
+ attribute :another_virtual_string, :string
46
+ end
47
+ end
48
+
49
+ include Module
50
+ end
51
+
52
+
53
+ class ObjectWithCallbacks < Object
54
+
55
+ before_save :before_save_callback
56
+ before_validation :before_validation_callback
57
+ after_save :after_save_callback
58
+
59
+ def before_save_callback
60
+ end
61
+
62
+ def before_validation_callback
63
+ end
64
+
65
+ def after_save_callback
66
+ end
67
+
68
+ end
69
+
70
+ class Child < ActiveRecord::Base
71
+ end
72
+
73
+ class ObjectWithBelongsTo < Object
74
+
75
+ attribute :child_id, :integer
76
+
77
+ belongs_to :child
78
+
79
+ end
80
+
81
+
82
+ end
83
+
84
+
85
+ describe ActiveType::Object do
86
+
87
+ subject { ObjectSpec::Object.new }
88
+
89
+ describe 'constructors' do
90
+ subject { ObjectSpec::Object }
91
+
92
+ it_should_behave_like 'ActiveRecord-like constructors', { :virtual_string => "string", :virtual_integer => 100, :virtual_time => Time.now, :virtual_date => Date.today, :virtual_boolean => true }
93
+
94
+ end
95
+
96
+ describe 'mass assignment' do
97
+ it_should_behave_like 'ActiveRecord-like mass assignment', { :virtual_string => "string", :virtual_integer => 100, :virtual_time => Time.now, :virtual_date => Date.today, :virtual_boolean => true }
98
+ end
99
+
100
+ describe 'accessors' do
101
+ it_should_behave_like 'ActiveRecord-like accessors', { :virtual_string => "string", :virtual_integer => 100, :virtual_time => Time.now, :virtual_date => Date.today, :virtual_boolean => true }
102
+ end
103
+
104
+ describe 'overridable attributes' do
105
+ subject { ObjectSpec::ObjectWithOverrides.new }
106
+
107
+ it 'is possible to override attributes with super' do
108
+ subject.overridable_test = "test"
109
+
110
+ subject.overridable_test.should == "testtest"
111
+ end
112
+ end
113
+
114
+ describe 'attribute name validation' do
115
+ it 'crashes when trying to define an invalid attribute name' do
116
+ klass = Class.new(ActiveType::Object)
117
+ expect {
118
+ klass.class_eval do
119
+ attribute :"<attr>", :string
120
+ end
121
+ }.to raise_error(ActiveType::InvalidAttributeNameError)
122
+ end
123
+ end
124
+
125
+ context 'coercible' do
126
+ describe 'string columns' do
127
+ it_should_behave_like 'a coercible string column', :virtual_string
128
+ end
129
+
130
+ describe 'integer columns' do
131
+ it_should_behave_like 'a coercible integer column', :virtual_integer
132
+ end
133
+
134
+ describe 'date columns' do
135
+ it_should_behave_like 'a coercible date column', :virtual_date
136
+ end
137
+
138
+ describe 'time columns' do
139
+ it_should_behave_like 'a coercible time column', :virtual_time
140
+ end
141
+
142
+ describe 'boolean columns' do
143
+ it_should_behave_like 'a coercible boolean column', :virtual_boolean
144
+ end
145
+ end
146
+
147
+ describe 'inherited classes' do
148
+
149
+ it 'sees attributes of both classes' do
150
+ object = ObjectSpec::InheritingObject.new
151
+ object.virtual_string = "string"
152
+ object.another_virtual_string = "another string"
153
+
154
+ object.virtual_string.should == "string"
155
+ object.another_virtual_string.should == "another string"
156
+ end
157
+
158
+ it 'does not define the attribute on the parent class' do
159
+ object = ObjectSpec::Object.new
160
+ object.should_not respond_to(:another_virtual_string)
161
+ end
162
+
163
+ end
164
+
165
+ describe 'included modules' do
166
+ it 'sees attributes of the included module' do
167
+ object = ObjectSpec::IncludingObject.new
168
+ object.virtual_string = "string"
169
+ object.another_virtual_string = "another string"
170
+
171
+ object.virtual_string.should == "string"
172
+ object.another_virtual_string.should == "another string"
173
+ end
174
+
175
+ it 'does not define the attribute on the parent class' do
176
+ object = ObjectSpec::Object.new
177
+ object.should_not respond_to(:another_virtual_string)
178
+ end
179
+ end
180
+
181
+ describe 'validations' do
182
+ subject { ObjectSpec::ObjectWithValidations.new }
183
+
184
+ it { should have(1).error_on(:virtual_string) }
185
+
186
+ it 'has no errors if validations pass' do
187
+ subject.virtual_string = "foo"
188
+
189
+ subject.should be_valid
190
+ subject.should have(:no).errors_on(:virtual_string)
191
+ end
192
+
193
+ it 'causes #save to return false' do
194
+ subject.save.should be_false
195
+ end
196
+ end
197
+
198
+ describe '#belongs_to' do
199
+ subject { ObjectSpec::ObjectWithBelongsTo.new }
200
+
201
+ it_should_behave_like 'a belongs_to association', :child, ObjectSpec::Child
202
+ end
203
+
204
+ describe '#save' do
205
+ subject { ObjectSpec::ObjectWithCallbacks.new }
206
+
207
+ it "returns true" do
208
+ subject.save
209
+ end
210
+
211
+ %w[before_validation before_save after_save].each do |callback|
212
+
213
+ it "calls #{callback}" do
214
+ subject.should_receive("#{callback}_callback")
215
+
216
+ subject.save.should be_true
217
+ end
218
+
219
+ end
220
+
221
+ %w[before_validation before_save].each do |callback|
222
+
223
+ it "aborts the chain when #{callback} returns false" do
224
+ subject.stub("#{callback}_callback" => false)
225
+
226
+ subject.save.should be_false
227
+ end
228
+
229
+ end
230
+
231
+ end
232
+
233
+ describe '.find' do
234
+ it 'raises an error' do
235
+ expect do
236
+ ObjectSpec::Object.find(1)
237
+ end.to raise_error(ActiveRecord::RecordNotFound)
238
+ end
239
+ end
240
+
241
+ describe '.all' do
242
+ it 'returns []' do
243
+ ObjectSpec::Object.all.should == []
244
+ end
245
+ end
246
+
247
+ describe '.create' do
248
+ it 'returns an object' do
249
+ object = ObjectSpec::Object.create(:virtual_string => "string")
250
+
251
+ object.should be_a(ObjectSpec::Object)
252
+ object.virtual_string.should == "string"
253
+ end
254
+ end
255
+
256
+ end