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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b7a8aaa4de25bb39e76fe2f9d62ee07650ab25c2
4
+ data.tar.gz: e9f689b6b22a2e253f6c7d191bf6ae9b757c149f
5
+ SHA512:
6
+ metadata.gz: 5306d05e6793c5656ee6afebd49bc70b3579667f94cee32f23a3f5ac834bc920fd48bc278c730a114493298fc173e0f43a72188681da896cf3b6af92e43991c8
7
+ data.tar.gz: bd0bfbe15cfe7dc610feaf52e75246ad28ea93419e1fc7e90589b3f9f57736f442a6558d0a8d4752c3d060cd20299bd2ee86cf0cf5841ba48c06b6bf5746bb91
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ doc
2
+ pkg
3
+ tags
4
+ *.gem
5
+ .idea
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ - "2.1.0"
6
+ gemfile:
7
+ - gemfiles/Gemfile.3.2
8
+ - gemfiles/Gemfile.4.0
9
+ - gemfiles/Gemfile.4.1
10
+ script: bundle exec rspec spec
11
+ notifications:
12
+ email:
13
+ - fail@makandra.de
14
+ branches:
15
+ only:
16
+ - master
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Tobias Kraze
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,149 @@
1
+ ActiveType [![Build Status](https://travis-ci.org/makandra/active_type.svg?branch=master)](https://travis-ci.org/makandra/active_type)
2
+ ==========
3
+
4
+ Make any Ruby object quack like ActiveRecord
5
+ --------------------------------------------
6
+
7
+ ActiveType is our take on "presenter models" (or "form models") in Rails. We want to have controllers (and forms) talk to models that are either not backed by a database table, or have additional functionality that should not be shared to the rest of the application.
8
+
9
+ However, we do not want to lose ActiveRecord's amenities, like validations, callbacks, etc.
10
+
11
+ Examples for use cases are models to support sign in:
12
+
13
+ ```ruby
14
+ class SignIn < ActiveType::Object
15
+
16
+ # this is not backed by a db table
17
+
18
+ attribute :username, :string
19
+ attribute :password, :string
20
+
21
+ validates :username, presence: true
22
+ validates :password, presence: true
23
+
24
+ # ...
25
+
26
+ end
27
+ ```
28
+
29
+ Or models to support sign up:
30
+
31
+ ```ruby
32
+ class User < ActiveRecord::Base
33
+ # ...
34
+ end
35
+
36
+ class SignUp < ActiveType::Record[User]
37
+
38
+ # this inherits from User
39
+
40
+ validates :password, confirmation: true
41
+
42
+ after_create :send_confirmation_email
43
+
44
+ def send_confirmation_email
45
+ # this should happen on sign-up, but not when creating a user in tests etc.
46
+ end
47
+
48
+ # ...
49
+
50
+ end
51
+ ```
52
+
53
+ ### ActiveType::Object
54
+
55
+
56
+ Inherit from `ActiveType::Object` if you want an `ActiveRecord`-kind class that is not backed by a database table.
57
+
58
+ You can define "columns" by saying `attribute`:
59
+
60
+ ```ruby
61
+ class SignIn < ActiveType::Object
62
+
63
+ attribute :email, :string
64
+ attribute :date_of_birth, :date
65
+ attribute :accepted_terms, :boolean
66
+
67
+ end
68
+ ```
69
+
70
+ These attributes can be assigned via constructor, mass-assignment, and are automatically typecast:
71
+
72
+ ```ruby
73
+ sign_in = SignIn.new(date_of_birth: "1980-01-01", accepted_terms: "1")
74
+ sign_in.date_of_birth.class # Date
75
+ sign_in.accepted_terms # true
76
+ ```
77
+
78
+ **`ActiveType::Object` actually inherits from `ActiveRecord::Base`, but simply skips all database access, inspired by [ActiveRecord Tableless](https://github.com/softace/activerecord-tableless).**
79
+
80
+ This means your object has all usual `ActiveRecord::Base` methods. Some of those might not work properly, however. What does work:
81
+
82
+ - validations
83
+ - callbacks (use `before_save`, `after_save`, not `before_create`, or `before_update`)
84
+ - "saving" (returning `true` or `false`, without actually persisting)
85
+ - belongs_to (after saying `attribute :child_id, :integer`)
86
+
87
+
88
+ ### ActiveType::Record
89
+
90
+ `ActiveType::Record` is simply `ActiveRecord::Base` plus the (virtual) attributes from `ActiveType::Object`. You can declare, assign, validate etc those attributes, but they will not be persisted.
91
+
92
+
93
+ ### ActiveType::Record[BaseClass]
94
+
95
+ `ActiveType::Record[BaseClass]` is used to extend a given `BaseClass` (that itself has to be an `ActiveRecord` model) with additional functionality, that is not meant to be shared to the rest of the application.
96
+
97
+ You class will inherit from `BaseClass`. You can add additional methods, validations, callbacks, as well as use (virtual) attributes like an `ActiveType::Object`:
98
+
99
+ ```ruby
100
+ class SignUp < ActiveType::Record[User]
101
+ # ...
102
+ end
103
+ ```
104
+
105
+
106
+
107
+ Supported Rails versions
108
+ ------------------------
109
+
110
+ ActiveType is tested against ActiveRecord 3.2, 4.0 and 4.1.
111
+
112
+ Later versions might work, earlier version will not.
113
+
114
+
115
+ Installation
116
+ ------------
117
+
118
+ In your `Gemfile` say:
119
+
120
+ gem 'active_type'
121
+
122
+ Now run `bundle install` and restart your server.
123
+
124
+
125
+ Development
126
+ -----------
127
+
128
+ - We run tests against several ActiveRecord versions.
129
+ - You can bundle all versions with `rake all:bundle`.
130
+ - You can run specs against all versions with `rake`.
131
+ - You can run specs against a single version with `VERSION=4.0 rake`.
132
+
133
+ If you would like to contribute:
134
+
135
+ - Fork the repository.
136
+ - Push your changes **with passing specs**.
137
+ - Send us a pull request.
138
+
139
+ I'm very eager to keep this gem leightweight and on topic. If you're unsure whether a change would make it into the gem, [talk to me beforehand](mailto:henning.koch@makandra.de).
140
+
141
+
142
+ Credits
143
+ -------
144
+
145
+ Tobias Kraze from [makandra](http://makandra.com/)
146
+
147
+ Henning Koch from [makandra](http://makandra.com/)
148
+
149
+
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'rake'
2
+ require 'bundler/gem_tasks'
3
+
4
+ desc 'Default: Run all specs.'
5
+ task :default => 'all:spec'
6
+
7
+
8
+ namespace :all do
9
+
10
+ desc "Run specs on all versions"
11
+ task :spec do
12
+ success = true
13
+ for_each_gemfile do
14
+ env = "SPEC=../../#{ENV['SPEC']} " if ENV['SPEC']
15
+ success &= system("#{env} bundle exec rspec spec")
16
+ end
17
+ fail "Tests failed" unless success
18
+ end
19
+
20
+ desc "Bundle all versions"
21
+ task :bundle do
22
+ for_each_gemfile do
23
+ system('bundle install')
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ def for_each_gemfile
30
+ version = ENV['VERSION'] || '*'
31
+ Dir["gemfiles/Gemfile.#{version}"].sort.each do |gemfile|
32
+ next if gemfile =~ /.lock/
33
+ puts '', "\033[44m#{gemfile}\033[0m", ''
34
+ ENV['BUNDLE_GEMFILE'] = gemfile
35
+ yield
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "active_type/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'active_type'
6
+ s.version = ActiveType::VERSION
7
+ s.authors = ["Tobias Kraze", "Henning Koch"]
8
+ s.email = 'tobias.kraze@makandra.de'
9
+ s.homepage = 'https://github.com/makandra/active_type'
10
+ s.summary = 'Make any Ruby object quack like ActiveRecord'
11
+ s.description = s.summary
12
+ s.license = 'MIT'
13
+
14
+ s.files = `git ls-files -z`.split("\x0")
15
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_development_dependency "bundler", "~> 1.5"
20
+ s.add_development_dependency "rake"
21
+
22
+ s.add_runtime_dependency('activerecord', '>= 3.2')
23
+
24
+ end
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~>3.2.0'
4
+ gem 'rspec'
5
+ gem 'sqlite3'
6
+
7
+ gem 'active_type', :path => '..'
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ active_type (0.1.0)
5
+ activerecord (>= 3.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (3.2.17)
11
+ activesupport (= 3.2.17)
12
+ builder (~> 3.0.0)
13
+ activerecord (3.2.17)
14
+ activemodel (= 3.2.17)
15
+ activesupport (= 3.2.17)
16
+ arel (~> 3.0.2)
17
+ tzinfo (~> 0.3.29)
18
+ activesupport (3.2.17)
19
+ i18n (~> 0.6, >= 0.6.4)
20
+ multi_json (~> 1.0)
21
+ arel (3.0.3)
22
+ builder (3.0.4)
23
+ diff-lcs (1.2.5)
24
+ i18n (0.6.9)
25
+ multi_json (1.9.2)
26
+ rspec (2.14.1)
27
+ rspec-core (~> 2.14.0)
28
+ rspec-expectations (~> 2.14.0)
29
+ rspec-mocks (~> 2.14.0)
30
+ rspec-core (2.14.8)
31
+ rspec-expectations (2.14.5)
32
+ diff-lcs (>= 1.1.3, < 2.0)
33
+ rspec-mocks (2.14.6)
34
+ sqlite3 (1.3.9)
35
+ tzinfo (0.3.39)
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ active_type!
42
+ activerecord (~> 3.2.0)
43
+ rspec
44
+ sqlite3
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~>4.0.0'
4
+ gem 'rspec'
5
+ gem 'sqlite3'
6
+
7
+ gem 'active_type', :path => '..'
@@ -0,0 +1,52 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ active_type (0.1.0)
5
+ activerecord (>= 3.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (4.0.4)
11
+ activesupport (= 4.0.4)
12
+ builder (~> 3.1.0)
13
+ activerecord (4.0.4)
14
+ activemodel (= 4.0.4)
15
+ activerecord-deprecated_finders (~> 1.0.2)
16
+ activesupport (= 4.0.4)
17
+ arel (~> 4.0.0)
18
+ activerecord-deprecated_finders (1.0.3)
19
+ activesupport (4.0.4)
20
+ i18n (~> 0.6, >= 0.6.9)
21
+ minitest (~> 4.2)
22
+ multi_json (~> 1.3)
23
+ thread_safe (~> 0.1)
24
+ tzinfo (~> 0.3.37)
25
+ arel (4.0.2)
26
+ atomic (1.1.16)
27
+ builder (3.1.4)
28
+ diff-lcs (1.2.5)
29
+ i18n (0.6.9)
30
+ minitest (4.7.5)
31
+ multi_json (1.9.2)
32
+ rspec (2.14.1)
33
+ rspec-core (~> 2.14.0)
34
+ rspec-expectations (~> 2.14.0)
35
+ rspec-mocks (~> 2.14.0)
36
+ rspec-core (2.14.8)
37
+ rspec-expectations (2.14.5)
38
+ diff-lcs (>= 1.1.3, < 2.0)
39
+ rspec-mocks (2.14.6)
40
+ sqlite3 (1.3.9)
41
+ thread_safe (0.3.1)
42
+ atomic (>= 1.1.7, < 2)
43
+ tzinfo (0.3.39)
44
+
45
+ PLATFORMS
46
+ ruby
47
+
48
+ DEPENDENCIES
49
+ active_type!
50
+ activerecord (~> 4.0.0)
51
+ rspec
52
+ sqlite3
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~>4.1.0.rc2'
4
+ gem 'rspec'
5
+ gem 'sqlite3'
6
+
7
+ gem 'active_type', :path => '..'
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ active_type (0.1.0)
5
+ activerecord (>= 3.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (4.1.0.rc2)
11
+ activesupport (= 4.1.0.rc2)
12
+ builder (~> 3.1)
13
+ activerecord (4.1.0.rc2)
14
+ activemodel (= 4.1.0.rc2)
15
+ activesupport (= 4.1.0.rc2)
16
+ arel (~> 5.0.0)
17
+ activesupport (4.1.0.rc2)
18
+ i18n (~> 0.6, >= 0.6.9)
19
+ json (~> 1.7, >= 1.7.7)
20
+ minitest (~> 5.1)
21
+ thread_safe (~> 0.1)
22
+ tzinfo (~> 1.1)
23
+ arel (5.0.0)
24
+ atomic (1.1.16)
25
+ builder (3.2.2)
26
+ diff-lcs (1.2.5)
27
+ i18n (0.6.9)
28
+ json (1.8.1)
29
+ minitest (5.3.1)
30
+ rspec (2.14.1)
31
+ rspec-core (~> 2.14.0)
32
+ rspec-expectations (~> 2.14.0)
33
+ rspec-mocks (~> 2.14.0)
34
+ rspec-core (2.14.8)
35
+ rspec-expectations (2.14.5)
36
+ diff-lcs (>= 1.1.3, < 2.0)
37
+ rspec-mocks (2.14.6)
38
+ sqlite3 (1.3.9)
39
+ thread_safe (0.3.1)
40
+ atomic (>= 1.1.7, < 2)
41
+ tzinfo (1.1.0)
42
+ thread_safe (~> 0.1)
43
+
44
+ PLATFORMS
45
+ ruby
46
+
47
+ DEPENDENCIES
48
+ active_type!
49
+ activerecord (~> 4.1.0.rc2)
50
+ rspec
51
+ sqlite3
@@ -0,0 +1,41 @@
1
+ module ActiveType
2
+
3
+ module ExtendedRecord
4
+
5
+ module Inheritance
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :extended_record_base_class
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ def model_name
16
+ extended_record_base_class.model_name
17
+ end
18
+
19
+ def sti_name
20
+ extended_record_base_class.sti_name
21
+ end
22
+
23
+
24
+ private
25
+
26
+ def find_sti_class(type_name)
27
+ sti_class = super
28
+ if self <= sti_class
29
+ self
30
+ else
31
+ sti_class
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_type/virtual_attributes'
2
+ require 'active_type/extended_record/inheritance'
3
+
4
+ module ActiveType
5
+
6
+ module ExtendedRecord
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+
12
+ def [](base)
13
+ Class.new(base) do
14
+ @abstract_class = true
15
+
16
+ include VirtualAttributes
17
+ include Inheritance
18
+
19
+ self.extended_record_base_class = base
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,65 @@
1
+ module ActiveType
2
+
3
+ module NoTable
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+
8
+ module ClassMethods
9
+
10
+ def columns
11
+ []
12
+ end
13
+
14
+ def destroy(*)
15
+ new
16
+ end
17
+
18
+ def destroy_all(*)
19
+ []
20
+ end
21
+
22
+
23
+ def find_by_sql(*)
24
+ []
25
+ end
26
+
27
+ end
28
+
29
+ def id
30
+ nil
31
+ end
32
+
33
+ def transaction(&block)
34
+ @_current_transaction_records ||= []
35
+ yield
36
+ end
37
+
38
+ def create(*)
39
+ true
40
+ end
41
+
42
+ def create_record(*)
43
+ true
44
+ end
45
+
46
+ def update(*)
47
+ true
48
+ end
49
+
50
+ def update_record(*)
51
+ true
52
+ end
53
+
54
+ def destroy
55
+ @destroyed = true
56
+ freeze
57
+ end
58
+
59
+ def reload
60
+ self
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_type/no_table'
2
+ require 'active_type/virtual_attributes'
3
+
4
+ module ActiveType
5
+
6
+ class Object < ActiveRecord::Base
7
+
8
+ include NoTable
9
+ include VirtualAttributes
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_type/virtual_attributes'
2
+ require 'active_type/extended_record'
3
+
4
+ module ActiveType
5
+
6
+ class Record < ActiveRecord::Base
7
+
8
+ @abstract_class = true
9
+
10
+ include VirtualAttributes
11
+ include ExtendedRecord
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveType
2
+ VERSION = '0.1.0'
3
+ end