whiteprint 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +251 -0
- data/Rakefile +55 -0
- data/lib/tasks/blueprint.rake +5 -0
- data/lib/whiteprint.rb +99 -0
- data/lib/whiteprint/adapters/active_record.rb +193 -0
- data/lib/whiteprint/adapters/active_record/has_and_belongs_to_many.rb +22 -0
- data/lib/whiteprint/adapters/active_record/migration.rb +17 -0
- data/lib/whiteprint/adapters/test.rb +20 -0
- data/lib/whiteprint/attributes.rb +207 -0
- data/lib/whiteprint/base.rb +95 -0
- data/lib/whiteprint/config.rb +19 -0
- data/lib/whiteprint/explanation.rb +73 -0
- data/lib/whiteprint/has_whiteprint.rb +7 -0
- data/lib/whiteprint/migrator.rb +66 -0
- data/lib/whiteprint/model.rb +25 -0
- data/lib/whiteprint/railtie.rb +24 -0
- data/lib/whiteprint/transform.rb +35 -0
- data/lib/whiteprint/version.rb +3 -0
- data/test/cases/active_record_test.rb +13 -0
- data/test/cases/attributes_test.rb +62 -0
- data/test/cases/blueprint_test.rb +125 -0
- data/test/cases/changes_tree_test.rb +51 -0
- data/test/cases/explanation_test.rb +32 -0
- data/test/cases/migrator_test.rb +70 -0
- data/test/models/car.rb +8 -0
- data/test/models/user.rb +8 -0
- data/test/schema.rb +11 -0
- data/test/test_helper.rb +21 -0
- data/vendor/active_support/concern.rb +142 -0
- metadata +270 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ac5d879aa5d240df5fb1689636e8c15462f9a192
|
4
|
+
data.tar.gz: 464a364c30c9edc45f9418d4f1f9ab8eaf57fb43
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 78358814f2b3e688cdb5720a4049c3bd3b0951d83f74627aaa754688ffa2b9792d5c8d44de038b824f44b785c55046b69299d84048a932f5157cfd223dde64f4
|
7
|
+
data.tar.gz: 2304c58e46b558e2833ebb1fe8d8aafed433e060a4f62afb814a1ea95389e5ab5793cdb82f04c06bee692bf8c9f753851b97325d299aaf25047eeef2d48bcc50
|
data/README.md
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
# Whiteprint
|
2
|
+
by [10KB](https://10kb.nl)
|
3
|
+
|
4
|
+
|
5
|
+
Whiteprint keeps track of the attributes of your models. It:
|
6
|
+
* Generates migrations for you if you update your model's whiteprint (only ActiveRecord at the moment)
|
7
|
+
* Provides you with helpers to use in your serializers or permitted attributes definition
|
8
|
+
* Can be extended with plugins
|
9
|
+
* Has support for inheritance and composition
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'whiteprint'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install whiteprint
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### 1. Add Whiteprint to your model
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class Car
|
31
|
+
include Whiteprint::Model
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
Alternatively, in an ActiveRecord model you could also use `has_whiteprint`.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class Car < ActiveRecord::Base
|
39
|
+
has_whiteprint
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
### 2. Add some attributes
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class Car < ActiveRecord::Base
|
47
|
+
include Whiteprint::Model
|
48
|
+
|
49
|
+
whiteprint do
|
50
|
+
string :brand, default: 'BMW'
|
51
|
+
string :name
|
52
|
+
text :description
|
53
|
+
decimal :price, precision: 5, scale: 10
|
54
|
+
end
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
### 3. Generate a migration
|
59
|
+
Let Whiteprint generate a migration to update your database schema for you (only ActiveRecord at the moment). Run:
|
60
|
+
|
61
|
+
```
|
62
|
+
rake whiteprint:migrate
|
63
|
+
```
|
64
|
+
|
65
|
+
Whiteprint will check all your models for changes and list them in your terminal. If multiple models have changes it will ask you if you want to apply these changes in one or separate migrations.
|
66
|
+
|
67
|
+
```
|
68
|
+
Whiteprint has detected 1 changes to your models.
|
69
|
+
+----------------------------+------------------------+--------------------------------------------+
|
70
|
+
| 1. Create a new table cars |
|
71
|
+
+----------------------------+------------------------+--------------------------------------------+
|
72
|
+
| name | type | options |
|
73
|
+
+----------------------------+------------------------+--------------------------------------------+
|
74
|
+
| brand | string | {:default=>"BMW"} |
|
75
|
+
| name | string | {} |
|
76
|
+
| description | text | {} |
|
77
|
+
| price | decimal | {:precision=>10, :scale=>5} |
|
78
|
+
| timestamps | | |
|
79
|
+
+----------------------------+------------------------+--------------------------------------------+
|
80
|
+
Migrations:
|
81
|
+
1. In one migration
|
82
|
+
2. In separate migrations
|
83
|
+
How would you like to process these changes?
|
84
|
+
> 1
|
85
|
+
How would you like to name this migration?
|
86
|
+
> Create cars
|
87
|
+
```
|
88
|
+
Your migration wil be **created** and **migrated**.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
# db/migrate/*********_create_cars.rb
|
92
|
+
|
93
|
+
class CreateCars < ActiveRecord::Migration
|
94
|
+
def change
|
95
|
+
create_table :cars do |t|
|
96
|
+
t.string :brand, {:default=>"BMW"}
|
97
|
+
t.string :name, {}
|
98
|
+
t.text :description, {}
|
99
|
+
t.decimal :price, {:precision=>10, :scale=>5}
|
100
|
+
t.timestamps
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
```
|
107
|
+
== 20160905153022 CreateCars: migrating =======================================
|
108
|
+
-- create_table(:cars)
|
109
|
+
-> 0.0081s
|
110
|
+
== 20160905153022 CreateCars: migrated (0.0082s) ==============================
|
111
|
+
```
|
112
|
+
|
113
|
+
### 4. Make some changes to your model
|
114
|
+
If we make some changes to our `Car` model and run `whiteprint:migrate` again, Whiteprint will detect these changes and create a migration to update your table.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
class Car < ActiveRecord::Base
|
118
|
+
include Whiteprint::Model
|
119
|
+
|
120
|
+
whiteprint do
|
121
|
+
string :brand, default: 'Ford'
|
122
|
+
string :name
|
123
|
+
decimal :price, precision: 10, scale: 5
|
124
|
+
references :color
|
125
|
+
end
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
```
|
130
|
+
> rake whiteprint:migrate
|
131
|
+
Whiteprint has detected 1 changes to your models.
|
132
|
+
+--------+-------------+------------+------------------+--------------------+----------------------+
|
133
|
+
| 1. Make changes to cars |
|
134
|
+
+--------+-------------+------------+------------------+--------------------+----------------------+
|
135
|
+
| action | name | type | type (currently) | options | options (currently) |
|
136
|
+
+--------+-------------+------------+------------------+--------------------+----------------------+
|
137
|
+
| added | color | references | | {} | |
|
138
|
+
| change | brand | string | string | {:default=>"Ford"} | {:default=>"BMW"} |
|
139
|
+
| remove | description | | | | |
|
140
|
+
+--------+-------------+------------+------------------+--------------------+----------------------+
|
141
|
+
Migrations:
|
142
|
+
1. In one migration
|
143
|
+
2. In separate migrations
|
144
|
+
How would you like to process these changes?
|
145
|
+
1
|
146
|
+
How would you like to name this migration?
|
147
|
+
Add color change default brand and remove description for cars
|
148
|
+
== 20160905162923 AddColorChangeDefaultBrandAndRemoveDescriptionForCars: migrating
|
149
|
+
-- change_table(:cars)
|
150
|
+
-> 0.0032s
|
151
|
+
== 20160905162923 AddColorChangeDefaultBrandAndRemoveDescriptionForCars: migrated (0.0034s)
|
152
|
+
```
|
153
|
+
|
154
|
+
## Adapters
|
155
|
+
Whiteprint is made to be persistence layer agnostic, but at this moment only an ActiveRecord adapter is implemented. If you would like to implement an adapter for another persistence layer please contact us. We'd love to help you.
|
156
|
+
|
157
|
+
An example of a Whiteprint adapter:
|
158
|
+
```ruby
|
159
|
+
module Whiteprint
|
160
|
+
module Adapters
|
161
|
+
class MyOwnAdapater < ::Whiteprint::Base
|
162
|
+
class << self
|
163
|
+
def applicable?(model)
|
164
|
+
# method used to automatically select an adapter for a model.
|
165
|
+
# for example:
|
166
|
+
model < MyOrm::Base
|
167
|
+
end
|
168
|
+
|
169
|
+
def generate_migration(name, trees)
|
170
|
+
# create a migration here given a set of trees with changes
|
171
|
+
# look at the activerecord adapter for further implementation details
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def persisted_attributes
|
176
|
+
# this method has to return the current attributes of the persistance layer
|
177
|
+
# return an instance of Whiteprint::Attributes
|
178
|
+
end
|
179
|
+
|
180
|
+
# The whiteprint do ... end block in your model is executed in the context of your adapter instance
|
181
|
+
# you can add methods to add functionality to your adapter. For example:
|
182
|
+
def address(name)
|
183
|
+
@attributes.add name: "#{name}_street", type: :text
|
184
|
+
@attributes.add name: "#{name}_house_number", type: :integer
|
185
|
+
@attributes.add name: "#{name}_city", type: :text
|
186
|
+
end
|
187
|
+
# And then you could do:
|
188
|
+
# class Company < MyOrm::Base
|
189
|
+
# include Whiteprint::Model
|
190
|
+
#
|
191
|
+
# whiteprint do
|
192
|
+
# address :office
|
193
|
+
# end
|
194
|
+
# end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
```
|
199
|
+
|
200
|
+
## ActiveRecord Adapter
|
201
|
+
The ActiveRecord adapter has some special properties which are explained in this section.
|
202
|
+
|
203
|
+
### Default id and timestamps
|
204
|
+
By default the adapter will add id and timestamps columns. You can disable this behaviour by passing arguments to the whiteprint method.
|
205
|
+
|
206
|
+
Model without an id:
|
207
|
+
```ruby
|
208
|
+
whiteprint(id: false) do
|
209
|
+
# ...
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
Model without timestamps:
|
214
|
+
```ruby
|
215
|
+
whiteprint(timestamps: false) do
|
216
|
+
# ...
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
### References
|
221
|
+
Adding an references columns will automatically set a `belongs_to` association on the model. Any options for the association can be passed in the whiteprint block.
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
whiteprint do
|
225
|
+
references :fileable, polymorphic: true
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
You can disable this behaviour by passing `auto_belongs_to: false` to the whiteprint method.
|
230
|
+
|
231
|
+
### Has and belongs to many
|
232
|
+
The activerecord adapter has support for a has_and_belongs_to_many attribute. This won't add a column to your model's table, but instead create a join table and set the association.
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
whiteprint do
|
236
|
+
has_and_belongs_to_many :categories
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
`habtm` is added as an alias
|
241
|
+
|
242
|
+
### Method as default value
|
243
|
+
|
244
|
+
### Accessor
|
245
|
+
|
246
|
+
## Attributes
|
247
|
+
|
248
|
+
## Configuration
|
249
|
+
|
250
|
+
## Origin
|
251
|
+
Whiteprint is extracted from an application framework we use internally. Right now, our framework is lacking tests and documentation, but we intend to open source more parts of our framework in the future.
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
|
5
|
+
test = File.expand_path('../test', __FILE__)
|
6
|
+
$LOAD_PATH.unshift(test) unless $LOAD_PATH.include?(test)
|
7
|
+
|
8
|
+
task default: :test
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
t.warning = true
|
15
|
+
end
|
16
|
+
|
17
|
+
namespace :analysis do
|
18
|
+
RuboCop::RakeTask.new
|
19
|
+
|
20
|
+
desc 'Analyze code style'
|
21
|
+
task style: [:rubocop]
|
22
|
+
|
23
|
+
desc 'Analyze code duplication'
|
24
|
+
task :duplication do
|
25
|
+
output = `bundle exec flay lib/`
|
26
|
+
if output.include? 'Similar code found'
|
27
|
+
puts output
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Analyze code complexity'
|
33
|
+
task :comlexity do
|
34
|
+
output = `bundle exec flog -m lib/`
|
35
|
+
exit_code = 0
|
36
|
+
minimum_score = 30
|
37
|
+
output = output.lines
|
38
|
+
|
39
|
+
# Skip total and average complexity score
|
40
|
+
output.shift
|
41
|
+
output.shift
|
42
|
+
|
43
|
+
output.each do |line|
|
44
|
+
score, method = line.split(' ')
|
45
|
+
score = score.to_i
|
46
|
+
|
47
|
+
if score > minimum_score
|
48
|
+
exit_code = 1
|
49
|
+
puts "High complexity in #{method}. Score: #{score}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
exit exit_code
|
54
|
+
end
|
55
|
+
end
|
data/lib/whiteprint.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'whiteprint/version'
|
2
|
+
|
3
|
+
module Whiteprint
|
4
|
+
require 'active_support/concern' unless defined?(ActiveSupport)
|
5
|
+
require 'active_support/inflections'
|
6
|
+
|
7
|
+
require 'parslet'
|
8
|
+
require 'terminal-table'
|
9
|
+
require 'highline'
|
10
|
+
|
11
|
+
require 'whiteprint/config'
|
12
|
+
require 'whiteprint/attributes'
|
13
|
+
require 'whiteprint/base'
|
14
|
+
require 'whiteprint/explanation'
|
15
|
+
require 'whiteprint/model'
|
16
|
+
require 'whiteprint/migrator'
|
17
|
+
require 'whiteprint/transform'
|
18
|
+
|
19
|
+
config do |c|
|
20
|
+
c.default_adapter = :base
|
21
|
+
c.eager_load = false
|
22
|
+
c.eager_load_paths = []
|
23
|
+
c.persisted_attribute_options = {
|
24
|
+
array: false,
|
25
|
+
limit: nil,
|
26
|
+
precision: nil,
|
27
|
+
scale: nil,
|
28
|
+
polymorphic: false,
|
29
|
+
null: true,
|
30
|
+
default: nil
|
31
|
+
}
|
32
|
+
c.meta_attribute_options = [:enum]
|
33
|
+
end
|
34
|
+
|
35
|
+
if defined?(ActiveRecord)
|
36
|
+
require 'whiteprint/has_whiteprint'
|
37
|
+
ActiveSupport.on_load :active_record do
|
38
|
+
ActiveRecord::Base.send :extend, Whiteprint::HasWhiteprint
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'whiteprint/railtie' if defined?(Rails)
|
43
|
+
|
44
|
+
require 'whiteprint/adapters/active_record'
|
45
|
+
require 'whiteprint/adapters/test'
|
46
|
+
|
47
|
+
ADAPTERS = {
|
48
|
+
active_record: Adapters::ActiveRecord,
|
49
|
+
has_and_belongs_to_many: Adapters::ActiveRecord::HasAndBelongsToMany,
|
50
|
+
test: Adapters::Test,
|
51
|
+
base: Base
|
52
|
+
}.freeze
|
53
|
+
|
54
|
+
class << self
|
55
|
+
def new(model, adapter: nil, **options)
|
56
|
+
if adapter
|
57
|
+
ADAPTERS[adapter].new(model, **options)
|
58
|
+
else
|
59
|
+
adapter = ADAPTERS.find do |_, whiteprint|
|
60
|
+
whiteprint.applicable?(model)
|
61
|
+
end
|
62
|
+
|
63
|
+
adapter[-1].new(model, **options)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
@@models = []
|
68
|
+
@@plugins = {}
|
69
|
+
|
70
|
+
def models=(models)
|
71
|
+
@@models = models
|
72
|
+
end
|
73
|
+
|
74
|
+
def models
|
75
|
+
@@models.select { |model| model.is_a?(Class) }
|
76
|
+
.reject { |model| model.respond_to?(:abstract_class) && model.abstract_class }
|
77
|
+
.sort_by { |model| model.respond_to?(:table_name) && model.table_name || model.name || model.object_id.to_s }
|
78
|
+
.sort { |a, b| -1 * (a <=> b).to_i }
|
79
|
+
.uniq { |model| model.respond_to?(:table_name) && model.table_name || model.name || model.object_id.to_s }
|
80
|
+
.sort_by { |model| model.name || model.object_id.to_s }
|
81
|
+
end
|
82
|
+
|
83
|
+
def whiteprints
|
84
|
+
models.map(&:whiteprint).compact
|
85
|
+
end
|
86
|
+
|
87
|
+
def changed_whiteprints
|
88
|
+
whiteprints.select(&:changes?)
|
89
|
+
end
|
90
|
+
|
91
|
+
def plugins
|
92
|
+
@@plugins
|
93
|
+
end
|
94
|
+
|
95
|
+
def register_plugin(name, constant)
|
96
|
+
@@plugins[name] = constant
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module Whiteprint
|
2
|
+
module Adapters
|
3
|
+
class ActiveRecord < ::Whiteprint::Base
|
4
|
+
require 'whiteprint/adapters/active_record/migration'
|
5
|
+
require 'whiteprint/adapters/active_record/has_and_belongs_to_many'
|
6
|
+
|
7
|
+
BELONGS_TO_OPTIONS = [:class_name, :anonymous_class, :foreign_key, :validate, :autosave,
|
8
|
+
:dependent, :primary_key, :inverse_of, :required, :foreign_type,
|
9
|
+
:polymorphic, :touch, :counter_cache, :cached]
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def applicable?(model)
|
13
|
+
return false unless defined?(::ActiveRecord)
|
14
|
+
model < ::ActiveRecord::Base
|
15
|
+
end
|
16
|
+
|
17
|
+
def underscore(name)
|
18
|
+
name = name.tr(' ', '_')
|
19
|
+
name.gsub(/([a-z])([A-Z])/) { "#{Regexp.last_match[1]}_#{Regexp.last_match[2].downcase}" }.downcase
|
20
|
+
end
|
21
|
+
|
22
|
+
def camelize(name)
|
23
|
+
name = underscore(name)
|
24
|
+
name = name.gsub(/^([a-z])/) { Regexp.last_match[1].upcase }
|
25
|
+
name.gsub(/_([a-zA-Z])/) { Regexp.last_match[1].upcase }
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate_migration(name, trees)
|
29
|
+
filename = "#{Time.now.strftime('%Y%m%d%H%M%S')}_#{underscore(name)}.rb"
|
30
|
+
File.open(File.join(Whiteprint.config.migration_path, filename), 'w') do |f|
|
31
|
+
f.write migration(name, trees)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def migration(name, trees)
|
36
|
+
"class #{camelize(name)} < ActiveRecord::Migration\n def change\n" + transform(trees) + " end\nend\n"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def transform(trees)
|
42
|
+
Migration.new.apply(trees).join("\n")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(model, id: true, timestamps: true, auto_belongs_to: true, **_options)
|
47
|
+
super(model, id: true, timestamps: true, **_options)
|
48
|
+
|
49
|
+
@has_id = id
|
50
|
+
@has_timestamps = timestamps
|
51
|
+
@auto_belongs_to = auto_belongs_to
|
52
|
+
@attributes.add(name: :id, type: :integer, null: false) if id
|
53
|
+
@attributes.add(name: :created_at, type: :datetime) if timestamps
|
54
|
+
@attributes.add(name: :updated_at, type: :datetime) if timestamps
|
55
|
+
end
|
56
|
+
|
57
|
+
def accessor(name, options)
|
58
|
+
@attributes.add(name: name, type: :accessor, virtual: true, **options)
|
59
|
+
model.send :attr_accessor, name
|
60
|
+
end
|
61
|
+
|
62
|
+
def changes_tree
|
63
|
+
changes_tree = super
|
64
|
+
|
65
|
+
unless changes_tree[:table_exists]
|
66
|
+
changes_tree[:has_id] = @has_id
|
67
|
+
changes_tree[:attributes].reject! { |attribute| attribute[:name] == :id }
|
68
|
+
end
|
69
|
+
|
70
|
+
added_created_at = changes_tree[:attributes].select { |attribute| attribute[:name] == :created_at && attribute[:kind] == :added }
|
71
|
+
added_updated_at = changes_tree[:attributes].select { |attribute| attribute[:name] == :updated_at && attribute[:kind] == :added }
|
72
|
+
|
73
|
+
if added_created_at.size == 1 && added_updated_at.size == 1
|
74
|
+
changes_tree[:attributes] -= [*added_created_at, *added_updated_at]
|
75
|
+
changes_tree[:attributes] += [{ type: :timestamps, kind: :added }]
|
76
|
+
end
|
77
|
+
|
78
|
+
changes_tree[:attributes].each do |attribute|
|
79
|
+
persisted_attribute = persisted_attributes[attribute[:name]]
|
80
|
+
if persisted_attribute && attribute[:options][:default].nil? && persisted_attribute[:options][:default]
|
81
|
+
changes_tree[:attributes] += [{ name: attribute[:name], kind: :removed_default }]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
changes_tree
|
86
|
+
end
|
87
|
+
|
88
|
+
def has_and_belongs_to_many(name, **options)
|
89
|
+
super(name, **options.merge(virtual: true))
|
90
|
+
model.send(:has_and_belongs_to_many, name.to_sym, **options) unless model.reflect_on_association(name)
|
91
|
+
|
92
|
+
association = model.reflect_on_association(name)
|
93
|
+
|
94
|
+
Class.new do
|
95
|
+
include Whiteprint::Model
|
96
|
+
|
97
|
+
@association = association
|
98
|
+
@join_table = association.join_table
|
99
|
+
|
100
|
+
whiteprint(adapter: :has_and_belongs_to_many, id: false, timestamps: false) do
|
101
|
+
integer association.foreign_key
|
102
|
+
integer association.association_foreign_key
|
103
|
+
end
|
104
|
+
|
105
|
+
class << self
|
106
|
+
def name
|
107
|
+
"#{@join_table}_habtm_model"
|
108
|
+
end
|
109
|
+
|
110
|
+
def table_name
|
111
|
+
@join_table
|
112
|
+
end
|
113
|
+
|
114
|
+
def table_exists?
|
115
|
+
::ActiveRecord::Base.connection.table_exists?(table_name)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
alias_method :habtm, :has_and_belongs_to_many
|
121
|
+
|
122
|
+
def migration(name)
|
123
|
+
self.class.migration(name, [changes_tree])
|
124
|
+
end
|
125
|
+
|
126
|
+
def options_from_column(column)
|
127
|
+
[:name, :type, *Whiteprint.config.persisted_attribute_options.keys].map do |option|
|
128
|
+
association_by_foreign_key = find_association_by_foreign_key(column)
|
129
|
+
overridden_name = association_by_foreign_key && association_by_foreign_key.name || column.name
|
130
|
+
current_attribute = attributes[overridden_name]
|
131
|
+
|
132
|
+
next {name: overridden_name} if option == :name && association_by_foreign_key
|
133
|
+
next {type: :references} if option == :type && association_by_foreign_key
|
134
|
+
next {polymorphic: true} if option == :polymorphic && association_by_foreign_key && model.column_names.include?(association_by_foreign_key.foreign_type)
|
135
|
+
next unless column.respond_to?(option)
|
136
|
+
next {default: current_attribute.default} if option == :default && current_attribute && current_attribute.default.is_a?(Symbol)
|
137
|
+
|
138
|
+
value = column.send(option)
|
139
|
+
value = column.type_cast_from_database(value) if option == :default
|
140
|
+
next if value == Whiteprint.config.persisted_attribute_options[option]
|
141
|
+
{ option => value }
|
142
|
+
end.compact.inject(&:merge)
|
143
|
+
end
|
144
|
+
|
145
|
+
def persisted_attributes
|
146
|
+
attributes = Whiteprint::Attributes.new
|
147
|
+
return attributes unless table_exists?
|
148
|
+
model.columns.each do |column|
|
149
|
+
next if find_association_by_foreign_type(column)
|
150
|
+
|
151
|
+
attributes.add options_from_column(column)
|
152
|
+
end
|
153
|
+
attributes.for_persisted
|
154
|
+
end
|
155
|
+
|
156
|
+
def references(name, **options)
|
157
|
+
super
|
158
|
+
return unless @auto_belongs_to
|
159
|
+
model.send :belongs_to, name.to_sym, **options.slice(*BELONGS_TO_OPTIONS)
|
160
|
+
end
|
161
|
+
|
162
|
+
def table_exists?
|
163
|
+
model.connection.schema_cache.clear!
|
164
|
+
model.connection.table_exists?(table_name)
|
165
|
+
end
|
166
|
+
|
167
|
+
def method_missing(type, name, **options)
|
168
|
+
super
|
169
|
+
|
170
|
+
if options[:default] && options[:default].is_a?(Symbol)
|
171
|
+
model.send :after_initialize do
|
172
|
+
next if self.send(name) || !new_record?
|
173
|
+
self.send "#{name}=", send(options[:default])
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def find_association_by_foreign_key(column)
|
181
|
+
model.reflect_on_all_associations.find do |association|
|
182
|
+
association.foreign_key == column.name
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def find_association_by_foreign_type(column)
|
187
|
+
model.reflect_on_all_associations.find do |association|
|
188
|
+
association.polymorphic? && association.foreign_type.to_s == column.name.to_s
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|