active_type 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,173 @@
1
+ require 'spec_helper'
2
+
3
+ module RecordSpec
4
+
5
+ class Record < ActiveType::Record
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
+ class RecordWithValidations < Record
16
+
17
+ validates :persisted_string, :presence => true
18
+ validates :virtual_string, :presence => true
19
+
20
+ end
21
+
22
+
23
+ class RecordWithOverrides < Record
24
+
25
+ attribute :overridable_test, :string
26
+
27
+ def overridable_test
28
+ super + super
29
+ end
30
+
31
+ end
32
+
33
+ class RecordCopy < ActiveType::Record
34
+ self.table_name = 'records'
35
+
36
+ attribute :virtual_string, :string
37
+
38
+ end
39
+
40
+ class OtherRecord < ActiveType::Record
41
+ end
42
+ end
43
+
44
+
45
+ describe ActiveType::Record do
46
+
47
+ subject { RecordSpec::Record.new }
48
+
49
+ it 'is a ActiveRecord::Base' do
50
+ subject.should be_a(ActiveRecord::Base)
51
+ end
52
+
53
+ it 'is an abstract class' do
54
+ ActiveType::Record.should be_abstract_class
55
+ end
56
+
57
+ describe 'constructors' do
58
+ subject { RecordSpec::Record }
59
+
60
+ it_should_behave_like 'ActiveRecord-like constructors', { :persisted_string => "string", :persisted_integer => 100, :persisted_time => Time.now, :persisted_date => Date.today, :persisted_boolean => true }
61
+
62
+ it_should_behave_like 'ActiveRecord-like constructors', { :virtual_string => "string", :virtual_integer => 100, :virtual_time => Time.now, :virtual_date => Date.today, :virtual_boolean => true }
63
+
64
+ end
65
+
66
+ describe 'mass assignment' do
67
+ it_should_behave_like 'ActiveRecord-like mass assignment', { :persisted_string => "string", :persisted_integer => 100, :persisted_time => Time.now, :persisted_date => Date.today, :persisted_boolean => true }
68
+
69
+ 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 }
70
+ end
71
+
72
+ describe 'accessors' do
73
+ it_should_behave_like 'ActiveRecord-like accessors', { :persisted_string => "string", :persisted_integer => 100, :persisted_time => Time.now, :persisted_date => Date.today, :persisted_boolean => true }
74
+
75
+ it_should_behave_like 'ActiveRecord-like accessors', { :virtual_string => "string", :virtual_integer => 100, :virtual_time => Time.now, :virtual_date => Date.today, :virtual_boolean => true }
76
+ end
77
+
78
+ describe 'overridable attributes' do
79
+
80
+ subject { RecordSpec::RecordWithOverrides.new }
81
+
82
+ it 'is possible to override attributes with super' do
83
+ subject.overridable_test = "test"
84
+
85
+ subject.overridable_test.should == "testtest"
86
+ end
87
+ end
88
+
89
+ describe 'attribute name validation' do
90
+ it 'crashes when trying to define an invalid attribute name' do
91
+ klass = Class.new(ActiveType::Record)
92
+ expect {
93
+ klass.class_eval do
94
+ attribute :"<attr>", :string
95
+ end
96
+ }.to raise_error(ActiveType::InvalidAttributeNameError)
97
+ end
98
+ end
99
+
100
+ describe '.reset_column_information' do
101
+ it 'does not affect virtual attributes' do
102
+ RecordSpec::RecordCopy.new.persisted_string = "string"
103
+ RecordSpec::RecordCopy.reset_column_information
104
+
105
+ expect do
106
+ RecordSpec::RecordCopy.new.virtual_string = "string"
107
+ end.to_not raise_error
108
+ end
109
+ end
110
+
111
+ context 'coercible' do
112
+ describe 'string columns' do
113
+ it_should_behave_like 'a coercible string column', :persisted_string
114
+ it_should_behave_like 'a coercible string column', :virtual_string
115
+ end
116
+
117
+ describe 'integer columns' do
118
+ it_should_behave_like 'a coercible integer column', :persisted_integer
119
+ it_should_behave_like 'a coercible integer column', :virtual_integer
120
+ end
121
+
122
+ describe 'date columns' do
123
+ it_should_behave_like 'a coercible date column', :persisted_date
124
+ it_should_behave_like 'a coercible date column', :virtual_date
125
+ end
126
+
127
+ describe 'time columns' do
128
+ it_should_behave_like 'a coercible time column', :persisted_time
129
+ it_should_behave_like 'a coercible time column', :virtual_time
130
+ end
131
+
132
+ describe 'boolean columns' do
133
+ it_should_behave_like 'a coercible boolean column', :persisted_boolean
134
+ it_should_behave_like 'a coercible boolean column', :virtual_boolean
135
+ end
136
+ end
137
+
138
+ describe 'validations' do
139
+ subject { RecordSpec::RecordWithValidations.new }
140
+
141
+ it { should have(1).error_on(:persisted_string) }
142
+ it { should have(1).error_on(:virtual_string) }
143
+ end
144
+
145
+ describe 'undefined columns' do
146
+ it 'raises an error when trying to access an undefined virtual attribute' do
147
+ expect do
148
+ subject.read_virtual_attribute('foo')
149
+ end.to raise_error(ActiveType::MissingAttributeError)
150
+ end
151
+ end
152
+
153
+ describe 'persistence' do
154
+
155
+ it 'persists to the database' do
156
+ subject.persisted_string = "persisted string"
157
+ subject.save.should be_true
158
+
159
+ subject.class.find(subject.id).persisted_string.should == "persisted string"
160
+ end
161
+ end
162
+
163
+ describe 'isolation' do
164
+ it 'does not let column information bleed into different models' do
165
+ record = RecordSpec::Record.new
166
+ other_record = RecordSpec::OtherRecord.new
167
+
168
+ record.should_not respond_to(:other_string)
169
+ other_record.should_not respond_to(:persisted_string)
170
+ end
171
+ end
172
+
173
+ end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ module SignInSpec
4
+
5
+ class SignIn < ActiveType::Object
6
+ attribute :email, :string
7
+ attribute :password, :string
8
+
9
+ validates :email, :presence => true
10
+ validates :password, :presence => true
11
+
12
+ validate :if => :password do |sign_in|
13
+ errors.add(:password, 'is not correct') unless sign_in.password == "correct password"
14
+ end
15
+
16
+ after_save :set_session
17
+
18
+ def set_session
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ describe SignInSpec::SignIn do
25
+
26
+ describe 'with missing credentials' do
27
+
28
+ it 'is invalid' do
29
+ subject.should_not be_valid
30
+ end
31
+
32
+ it 'has errors' do
33
+ subject.valid?
34
+ subject.errors[:email].should == ["can't be blank"]
35
+ subject.errors[:password].should == ["can't be blank"]
36
+ end
37
+
38
+ it 'does not save' do
39
+ subject.save.should be_false
40
+ end
41
+
42
+ it 'does not set the session' do
43
+ subject.should_not_receive :set_session
44
+ subject.save
45
+ end
46
+
47
+ end
48
+
49
+ describe 'with invalid credentials' do
50
+
51
+ before do
52
+ subject.email = "email"
53
+ subject.password = "incorrect password"
54
+ end
55
+
56
+ it 'is invalid' do
57
+ subject.should_not be_valid
58
+ end
59
+
60
+ it 'has errors' do
61
+ subject.valid?
62
+ subject.errors[:password].should == ["is not correct"]
63
+ end
64
+
65
+ it 'does not save' do
66
+ subject.save.should be_false
67
+ end
68
+
69
+ it 'does not set the session' do
70
+ subject.should_not_receive :set_session
71
+ subject.save
72
+ end
73
+
74
+ end
75
+
76
+ describe 'with valid credentials' do
77
+
78
+ before do
79
+ subject.email = "email"
80
+ subject.password = "correct password"
81
+ end
82
+
83
+ it 'is invalid' do
84
+ subject.should be_valid
85
+ end
86
+
87
+ it 'does save' do
88
+ subject.save.should be_true
89
+ end
90
+
91
+ it 'sets the session' do
92
+ subject.should_receive :set_session
93
+ subject.save
94
+ end
95
+
96
+ end
97
+
98
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ module SignUpSpec
4
+
5
+ class User < ActiveType::Record
6
+ validates :email, :presence => true
7
+ validates :password, :presence => true
8
+ end
9
+
10
+
11
+ class SignUp < ActiveType::Record[User]
12
+ attribute :terms, :boolean
13
+
14
+ validates :terms, :acceptance => {:allow_nil => false, :accept => true}
15
+
16
+ after_create :send_welcome_email
17
+
18
+ def send_welcome_email
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+
25
+ describe SignUpSpec::User do
26
+
27
+ it 'is valid without a password confirmation' do
28
+ subject.email = "email"
29
+ subject.password = "password"
30
+
31
+ subject.should be_valid
32
+ end
33
+
34
+ end
35
+
36
+
37
+ describe SignUpSpec::SignUp do
38
+
39
+ it 'is invalid without an email' do
40
+ subject.password = "password"
41
+ subject.terms = true
42
+
43
+ subject.should_not be_valid
44
+ subject.errors['email'].should == ["can't be blank"]
45
+ end
46
+
47
+ it 'is invalid without accepted terms' do
48
+ subject.email = "email"
49
+ subject.password = "password"
50
+
51
+ subject.should_not be_valid
52
+ subject.errors['terms'].should == ["must be accepted"]
53
+ end
54
+
55
+ context 'with invalid data' do
56
+
57
+ it 'does not save' do
58
+ subject.save.should be_false
59
+ end
60
+
61
+ it 'does not send an email' do
62
+ subject.should_not_receive :send_welcome_email
63
+ subject.save
64
+ end
65
+
66
+ end
67
+
68
+ context 'with valid data' do
69
+
70
+ before do
71
+ subject.email = "email"
72
+ subject.password = "password"
73
+ subject.terms = "1"
74
+ end
75
+
76
+ it 'does save' do
77
+ subject.valid?
78
+ subject.save.should be_true
79
+ end
80
+
81
+ it 'sends the email' do
82
+ subject.should_receive :send_welcome_email
83
+
84
+ subject.save
85
+ end
86
+
87
+ end
88
+
89
+
90
+ end
@@ -0,0 +1,24 @@
1
+ shared_examples_for "ActiveRecord-like accessors" do |attributes|
2
+
3
+ it 'allows to read and write' do
4
+ attributes.each do |key, value|
5
+ subject.send("#{key}=", value)
6
+ subject.send(key).should == value
7
+ end
8
+ end
9
+
10
+ it 'allows to read via []' do
11
+ attributes.each do |key, value|
12
+ subject.send("#{key}=", value)
13
+ subject[key].should == value
14
+ end
15
+ end
16
+
17
+ it 'allows to write via []=' do
18
+ attributes.each do |key, value|
19
+ subject[key] = value
20
+ subject.send(key).should == value
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,17 @@
1
+ shared_examples_for 'a belongs_to association' do |association, klass|
2
+
3
+ let(:record) { klass.create }
4
+
5
+ it 'sets the id when assigning a record' do
6
+ subject.send("#{association}=", record)
7
+
8
+ subject.send("#{association}_id").should == record.id
9
+ end
10
+
11
+ it 'sets the record when assigning an id' do
12
+ subject.send("#{association}_id=", record.id)
13
+
14
+ subject.send("#{association}").should == record
15
+ end
16
+
17
+ end
@@ -0,0 +1,203 @@
1
+ module TimeConversionSpec
2
+ class Record < ActiveRecord::Base
3
+ end
4
+ end
5
+
6
+ shared_examples_for 'a coercible string column' do |column|
7
+
8
+ it 'is nil by default' do
9
+ subject.send(column).should be_nil
10
+ end
11
+
12
+ it 'leaves strings alone' do
13
+ subject.send(:"#{column}=", "string")
14
+
15
+ subject.send(column).should == "string"
16
+ end
17
+
18
+ it 'does not convert blank' do
19
+ subject.send(:"#{column}=", "")
20
+
21
+ subject.send(column).should == ""
22
+ end
23
+
24
+ end
25
+
26
+
27
+ shared_examples_for 'a coercible integer column' do |column|
28
+
29
+ it 'is nil by default' do
30
+ subject.send(column).should be_nil
31
+ end
32
+
33
+ it 'leaves integers alone' do
34
+ subject.send(:"#{column}=", 10)
35
+
36
+ subject.send(column).should == 10
37
+ end
38
+
39
+ it 'converts strings to integers' do
40
+ subject.send(:"#{column}=", "10")
41
+
42
+ subject.send(column).should == 10
43
+ end
44
+
45
+ it 'converts blank to nil' do
46
+ subject.send(:"#{column}=", "")
47
+
48
+ subject.send(column).should be_nil
49
+ end
50
+
51
+ end
52
+
53
+
54
+ shared_examples_for 'a coercible date column' do |column|
55
+
56
+ it 'is nil by default' do
57
+ subject.send(column).should be_nil
58
+ end
59
+
60
+ it 'leaves dates alone' do
61
+ date = Date.today
62
+ subject.send(:"#{column}=", date)
63
+
64
+ subject.send(column).should == date
65
+ end
66
+
67
+ it 'converts strings to dates' do
68
+ subject.send(:"#{column}=", "2010-10-01")
69
+
70
+ subject.send(column).should == Date.new(2010, 10, 1)
71
+ end
72
+
73
+ it 'converts blank to nil' do
74
+ subject.send(:"#{column}=", "")
75
+
76
+ subject.send(column).should be_nil
77
+ end
78
+
79
+ end
80
+
81
+
82
+ shared_examples_for 'a coercible time column' do |column|
83
+
84
+ around do |example|
85
+ begin
86
+ old_time_zone = Time.zone
87
+ old_time_zone_aware_attributes = ActiveRecord::Base.time_zone_aware_attributes
88
+ old_default_timezone = ActiveRecord::Base.default_timezone
89
+ example.run
90
+ ensure
91
+ Time.zone = old_time_zone
92
+ ActiveRecord::Base.time_zone_aware_attributes = old_time_zone_aware_attributes
93
+ ActiveRecord::Base.default_timezone = old_default_timezone
94
+ subject.class.reset_column_information
95
+ end
96
+ end
97
+
98
+ def it_should_convert_like_active_record(column)
99
+ time = "2010-10-01 12:15"
100
+ TimeConversionSpec::Record.reset_column_information
101
+ subject.class.reset_column_information
102
+
103
+ comparison = TimeConversionSpec::Record.new
104
+ subject.send(:"#{column}=", time)
105
+ comparison.persisted_time = time
106
+
107
+ result = subject.send(column)
108
+ result.should == comparison.persisted_time
109
+ result.zone.should == comparison.persisted_time.zone
110
+ end
111
+
112
+
113
+ it 'is nil by default' do
114
+ subject.send(column).should be_nil
115
+ end
116
+
117
+ it 'leaves times alone' do
118
+ time = Time.now
119
+ subject.send(:"#{column}=", time)
120
+
121
+ subject.send(column).should == time
122
+ end
123
+
124
+ it 'converts strings to times' do
125
+ subject.send(:"#{column}=", "2010-10-01 12:15")
126
+
127
+ subject.send(column).should == Time.new(2010, 10, 1, 12, 15)
128
+ end
129
+
130
+ it 'behaves consistently with ActiveRecord' do
131
+ Time.zone = 'Hawaii'
132
+
133
+ it_should_convert_like_active_record(column)
134
+ end
135
+
136
+ it 'behaves consistently with ActiveRecord if time_zone_aware_attributes is set' do
137
+ Time.zone = 'Hawaii'
138
+ ActiveRecord::Base.time_zone_aware_attributes = true
139
+
140
+ it_should_convert_like_active_record(column)
141
+ end
142
+
143
+ it 'behaves consistently with ActiveRecord if default_timezone is :utc' do
144
+ Time.zone = 'Hawaii'
145
+ ActiveRecord::Base.default_timezone = :utc
146
+
147
+ it_should_convert_like_active_record(column)
148
+ end
149
+
150
+ it 'behaves consistently with ActiveRecord if time_zone_aware_attributes is set, default_timezone is :utc' do
151
+ Time.zone = 'Hawaii'
152
+ ActiveRecord::Base.default_timezone = :utc
153
+ ActiveRecord::Base.time_zone_aware_attributes = true
154
+
155
+ it_should_convert_like_active_record(column)
156
+ end
157
+
158
+ end
159
+
160
+
161
+ shared_examples_for 'a coercible boolean column' do |column|
162
+
163
+ it 'is nil by default' do
164
+ subject.send(column).should be_nil
165
+ end
166
+
167
+ it 'leaves booleans alone' do
168
+ subject.send(:"#{column}=", true)
169
+
170
+ subject.send(column).should == true
171
+ end
172
+
173
+ it 'converts 1 to true' do
174
+ subject.send(:"#{column}=", "1")
175
+
176
+ subject.send(column).should == true
177
+ end
178
+
179
+ it 'converts 0 to false' do
180
+ subject.send(:"#{column}=", "0")
181
+
182
+ subject.send(column).should == false
183
+ end
184
+
185
+ it 'converts "" to nil' do
186
+ subject.send(:"#{column}=", "")
187
+
188
+ subject.send(column).should be_nil
189
+ end
190
+
191
+ it 'converts "true" to true' do
192
+ subject.send(:"#{column}=", "true")
193
+
194
+ subject.send(column).should == true
195
+ end
196
+
197
+ it 'converts "false" to false' do
198
+ subject.send(:"#{column}=", "false")
199
+
200
+ subject.send(column).should == false
201
+ end
202
+
203
+ end
@@ -0,0 +1,30 @@
1
+ shared_examples_for 'ActiveRecord-like constructors' do |attributes|
2
+
3
+ it 'return a new record' do
4
+ subject.new.should be_new_record
5
+ end
6
+
7
+ it 'assigns given attributes' do
8
+ record = subject.new(attributes)
9
+
10
+ attributes.each do |key, value|
11
+ record.send(key).should == value
12
+ end
13
+ end
14
+
15
+ if ActiveRecord::VERSION::MAJOR >= 4
16
+
17
+ it 'raises on unpermitted parameters' do
18
+ params = ProtectedParams.new(attributes)
19
+ expect { subject.new(params) }.to raise_error(ActiveModel::ForbiddenAttributesError)
20
+ end
21
+
22
+ it 'accepts permitted parameters' do
23
+ params = ProtectedParams.new(attributes)
24
+ params.permit!
25
+ expect { subject.new(params) }.to_not raise_error
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,26 @@
1
+ shared_examples_for 'ActiveRecord-like mass assignment' do |attributes|
2
+
3
+ it 'assigns all given attributes' do
4
+ subject.attributes = attributes
5
+
6
+ attributes.each do |key, value|
7
+ subject.send(key).should == value
8
+ end
9
+ end
10
+
11
+ if ActiveRecord::VERSION::MAJOR >= 4
12
+
13
+ it 'raises on unpermitted parameters' do
14
+ params = ProtectedParams.new(attributes)
15
+ expect { subject.attributes = params }.to raise_error(ActiveModel::ForbiddenAttributesError)
16
+ end
17
+
18
+ it 'accepts permitted parameters' do
19
+ params = ProtectedParams.new(attributes)
20
+ params.permit!
21
+ expect { subject.attributes = params }.to_not raise_error
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ $: << File.join(File.dirname(__FILE__), "/../../lib" )
4
+
5
+ require 'active_record'
6
+ require 'active_type'
7
+
8
+ ActiveRecord::Base.default_timezone = :local
9
+
10
+ Dir["#{File.dirname(__FILE__)}/support/*"].each {|f| require f}
11
+ Dir["#{File.dirname(__FILE__)}/shared_examples/*"].each {|f| require f}
12
+
13
+
14
+ RSpec.configure do |config|
15
+ config.around do |example|
16
+ ActiveRecord::Base.transaction do
17
+ begin
18
+ example.run
19
+ ensure
20
+ raise ActiveRecord::Rollback
21
+ end
22
+ end
23
+ end
24
+ end