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