trax_model 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +154 -2
- data/lib/trax.rb +2 -0
- data/lib/trax/model.rb +1 -1
- data/lib/trax/model/mti.rb +11 -0
- data/lib/trax/model/mti/abstract.rb +62 -0
- data/lib/trax/model/mti/entity.rb +63 -0
- data/lib/trax/model/mti/namespace.rb +21 -0
- data/lib/trax/model/registry.rb +1 -1
- data/lib/trax_model.rb +1 -1
- data/lib/trax_model/version.rb +1 -1
- data/trax_model.gemspec +1 -1
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72afe4ed91e48868ae5965523a0ece32221a2a42
|
4
|
+
data.tar.gz: 29e28997f27539058bf5a7e131d13cef8696608e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4e3d8332d31fbabdca1318bf3a504e775e9c1b080df5cd7de76150bdcf24b508786c5c5d2767da8628f933b32b9cb980fe54a9dcb659701a653c0ee3b57b919
|
7
|
+
data.tar.gz: 06619d0356c3b86ffd769c4410be2e59e40838ca6d0af1a60e8c88f40d113e0775dd95c3a6caf4499c29b8fa800bbc94efd0684e5434bdcc5e5e4e0a81f091a1
|
data/README.md
CHANGED
@@ -1,6 +1,158 @@
|
|
1
|
-
#
|
1
|
+
# Trax Model
|
2
|
+
|
3
|
+
A composeable companion to active record models. Just include ::Trax::Model and you're off to the races.
|
4
|
+
|
5
|
+
### UUIDS
|
6
|
+
|
7
|
+
Supports uuid prefixes, and recommends next uuid prefix based on all uuid prefixes defined
|
8
|
+
in system -- Makes your uuids more discoverable and allows you to identify the model
|
9
|
+
itself just by the uuid, or do even cooler stuff programatically
|
10
|
+
|
11
|
+
### ITS IMPERATIVE THAT YOU DO NOT CHANGE YOUR UUID PREFIXES AFTER CREATING RECORDS
|
12
|
+
|
13
|
+
Sorry for yelling, but the point is, that will throw all the mapping stuff out of whack.
|
14
|
+
Don't do it. Treat it as a single point of truth for identifying the model in your system.
|
15
|
+
|
16
|
+
### UUID Prefixes should be treated like an enum, values are ordered like
|
17
|
+
|
18
|
+
[0a, 0b, 0c...], from 0-9a-f, then back down in reverse, i.e.
|
19
|
+
|
20
|
+
[a0, a1, a2], the alpha first prefixes are a higher sort order in the list
|
21
|
+
|
22
|
+
### Why?
|
23
|
+
|
24
|
+
This library is compatible with postgres's uuid, hexidecimal type. This enables
|
25
|
+
you to use the library to generate uuids on the fly, in your application code,
|
26
|
+
WITHOUT relying on primary key integer increment, which in case you haven't yet realized,
|
27
|
+
is the wild west once you are processing enough writes that you start seeing duplicate
|
28
|
+
primary key errors.
|
29
|
+
|
30
|
+
### Can't there still be conflicts?
|
31
|
+
|
32
|
+
Yes but the odds are astronomically small. From what I understand, probably you are more
|
33
|
+
likely to get struck by lightning than see that error. With that said, I am overwriting
|
34
|
+
the first 2 generated characters of the uuid function with a fixed character string, which
|
35
|
+
may affect the stats slightly, however Im not even sure if thats in a negative manner,
|
36
|
+
based on the fact that it splits the likeleyhood of a collision per record type
|
37
|
+
|
38
|
+
I.E.
|
39
|
+
|
40
|
+
``` ruby
|
41
|
+
class Product < ActiveRecord::Base
|
42
|
+
include ::Trax::Model
|
43
|
+
defaults :uuid_prefix => "0a"
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
``` ruby
|
48
|
+
Product.new
|
49
|
+
|
50
|
+
=> #<Product id: nil, name: nil, category_id: nil, user_id: nil, price: nil, in_stock_quantity: nil, on_order_quantity: nil, active: nil, uuid: "0a97ad3e-1673-41f3-b356-d62dd53629d8", created_at: nil, updated_at: nil>
|
51
|
+
```
|
52
|
+
|
53
|
+
### Get next available prefix, useful when setting models up
|
54
|
+
|
55
|
+
``` ruby
|
56
|
+
bx rails c
|
57
|
+
::Trax::Model::Registry.next_prefix
|
58
|
+
=> "1a"
|
59
|
+
```
|
60
|
+
|
61
|
+
But wait theires more!
|
62
|
+
|
63
|
+
### UUID utility methods
|
64
|
+
|
65
|
+
``` ruby
|
66
|
+
product_uuid = Product.first.uuid
|
67
|
+
=> "0a97ad3e-1673-41f3-b356-d62dd53629d8"
|
68
|
+
|
69
|
+
product_uuid.record_type
|
70
|
+
=> Product
|
71
|
+
product_uuid.record
|
72
|
+
```
|
73
|
+
|
74
|
+
will return the product instance
|
75
|
+
|
76
|
+
Which opens up quite a few possibilites via the newfound discoverability of your uuids...
|
77
|
+
|
78
|
+
# MTI (Multiple Table Inheritance)
|
79
|
+
|
80
|
+
### Note: you must use Trax UUIDS w/ prefixes to use this feature (as we map each entity to its specific table, via the prefixed uuid.
|
81
|
+
|
82
|
+
Going to be a very brief documentation but:
|
83
|
+
|
84
|
+
### Set up MTI structure like this:
|
85
|
+
```
|
86
|
+
models/post.rb (your entity model)
|
87
|
+
models/post_types/abstract.rb (abstract, inherit from this)
|
88
|
+
models/post_types/entity.rb
|
89
|
+
models/post_types/video.rb
|
90
|
+
models/post_types/text.rb
|
91
|
+
models/post_types/audio.rb
|
92
|
+
```
|
93
|
+
|
94
|
+
Post is your entity class, entity is essentially a flat table which contains a list of
|
95
|
+
any common attributes, as well as the ids for each of your MTI data models. The beauty
|
96
|
+
of this, is that since Trax model uuids tell us what type the record is, we don't
|
97
|
+
need to use STI, or have a type column to determine the type of the record.
|
98
|
+
|
99
|
+
Basically, the entity model when loaded, will eager load the real model. If the real model is created, updated, or destroyed, a callback will ensure that the corresponding entity record is kept in sync.
|
100
|
+
|
101
|
+
``` ruby
|
102
|
+
module Blog
|
103
|
+
class Post < ActiveRecord::Base
|
104
|
+
include ::Trax::Model::MTI::Entity
|
105
|
+
|
106
|
+
mti_namespace ::Blog::Posts
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
#no database table to this class as its abstract.
|
111
|
+
module Blog
|
112
|
+
module Posts
|
113
|
+
class Abstract < ::ActiveRecord::Base
|
114
|
+
# following line sets abstract_class = true when including module
|
115
|
+
include ::Trax::Model::MTI::Abstract
|
116
|
+
|
117
|
+
entity_model :class_name => "Blog::Post"
|
118
|
+
|
119
|
+
### Define your base inherited logic / methods here ###
|
120
|
+
|
121
|
+
belongs_to :user_id
|
122
|
+
belongs_to :category_id
|
123
|
+
|
124
|
+
validates :user_id
|
125
|
+
validates :category_id
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
module Blog
|
131
|
+
module Posts
|
132
|
+
class Video < ::Blog::Posts::Abstract
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
### MTI VS STI
|
140
|
+
|
141
|
+
The main advantages of Multiple Table Inheritance versus Single Table inheritance I see are:
|
142
|
+
|
143
|
+
1) Table size. As databases grow, vertically adding more length to traverse the table will continue to get slower. One way to mitigate this issue would be to split up your table into multiple horizontal tables, if it makes sense for your data structure to do so (i.e. like above)
|
144
|
+
|
145
|
+
2) STI will get out of proportion likely. I.e. if 90% of your posts are text posts, then when you are looking for a video post, you are to some degree being slowed down by the video posts in your table. (or at least at some point when you reach past xxxxxx number of records)
|
146
|
+
|
147
|
+
3) Further on that note, only storing what you need for each individual subset of your data, on that particular subset. I.e. if video post has a video_url attribute, but none of the other post types have that, it will keep holes out of your data tables since video_url is only on the video_posts table.
|
148
|
+
|
149
|
+
4) Real separation at the data level of non common attributes, so you dont have to write safeguards in child classes to make sure that a value didnt slip into a field or whatever, because each child class has its own individual interpretation of the schema.
|
150
|
+
|
151
|
+
The main disadvantages are:
|
152
|
+
|
153
|
+
1) No shared view between child types. I.E. thats what the MTI entity is for. (want to find all blog posts? You cant unless you select a type first, or are using this gem, or a postgres view or something else)
|
154
|
+
2) More difficult to setup since each child table needs its own table.
|
2
155
|
|
3
|
-
TODO: Write a gem description
|
4
156
|
|
5
157
|
## Installation
|
6
158
|
|
data/lib/trax.rb
CHANGED
data/lib/trax/model.rb
CHANGED
@@ -21,6 +21,7 @@ module Trax
|
|
21
21
|
autoload :UUIDPrefix
|
22
22
|
autoload :UniqueId
|
23
23
|
autoload :Matchable
|
24
|
+
autoload :MTI
|
24
25
|
autoload :Validators
|
25
26
|
|
26
27
|
include ::Trax::Model::Matchable
|
@@ -34,7 +35,6 @@ module Trax
|
|
34
35
|
register_trax_models(self)
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
38
|
module ClassMethods
|
39
39
|
delegate :register_trax_model, :to => "::Trax::Model::Registry"
|
40
40
|
delegate :[], :to => :find
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Trax
|
2
|
+
module Model
|
3
|
+
module MTI
|
4
|
+
module Abstract
|
5
|
+
extend ::ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :mti_config
|
9
|
+
|
10
|
+
self.abstract_class = true
|
11
|
+
self.mti_config = ::Hashie::Mash.new(:foreign_key => :id)
|
12
|
+
|
13
|
+
scope :records, lambda{
|
14
|
+
map(&:entity)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def inherited(subklass)
|
20
|
+
super(subklass)
|
21
|
+
|
22
|
+
subklass.after_create do |record|
|
23
|
+
entity_model = mti_config[:class_name].constantize.new
|
24
|
+
|
25
|
+
record.attributes.each_pair do |k,v|
|
26
|
+
entity_model.__send__("#{k}=", v) if entity_model.respond_to?(k)
|
27
|
+
end
|
28
|
+
|
29
|
+
entity_model.save
|
30
|
+
end
|
31
|
+
|
32
|
+
subklass.after_update do |record|
|
33
|
+
entity_model = record.entity
|
34
|
+
|
35
|
+
if record.changed.any?
|
36
|
+
record.changes.each_pair do |k,v|
|
37
|
+
entity_model.__send__("#{k}=", v[1]) if entity.respond_to?(:"#{k}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
entity_model.save if entity_model.changed.any?
|
42
|
+
end
|
43
|
+
|
44
|
+
subklass.after_destroy do |record|
|
45
|
+
entity_model = record.entity
|
46
|
+
|
47
|
+
entity_model.destroy
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def entity_model(options)
|
52
|
+
valid_options = options.assert_valid_keys(:class_name, :foreign_key)
|
53
|
+
mti_config.merge!(valid_options)
|
54
|
+
|
55
|
+
self.has_one(:entity, mti_config.symbolize_keys)
|
56
|
+
self.accepts_nested_attributes_for(:entity)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Trax
|
2
|
+
module Model
|
3
|
+
module MTI
|
4
|
+
module Entity
|
5
|
+
extend ::ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :_mti_namespace
|
9
|
+
|
10
|
+
scope :records, lambda{
|
11
|
+
map(&:entity)
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def model_type
|
16
|
+
@model_type ||= uuid.record_type
|
17
|
+
end
|
18
|
+
|
19
|
+
def model_type_key
|
20
|
+
:"#{model_type.name.demodulize.underscore}_entity"
|
21
|
+
end
|
22
|
+
|
23
|
+
def model
|
24
|
+
@model ||= __send__(model_type_key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def uuid
|
28
|
+
@uuid ||= self[:id].uuid
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def all
|
33
|
+
relation = super
|
34
|
+
entity_ids = relation.pluck(:id)
|
35
|
+
entity_types = entity_ids.map { |id| ::Trax::Model::UUID.new(id[0..1]) }
|
36
|
+
.map(&:record_type)
|
37
|
+
.flatten
|
38
|
+
.compact
|
39
|
+
.uniq
|
40
|
+
|
41
|
+
relation_names = entity_types.map{ |type| :"#{type.name.demodulize.underscore}_entity" }
|
42
|
+
|
43
|
+
relation.includes(relation_names).references(relation_names)
|
44
|
+
end
|
45
|
+
|
46
|
+
def mti_namespace(namespace)
|
47
|
+
_mti_namespace = (namespace.is_a?(String)) ? namespace.constantize : namespace
|
48
|
+
|
49
|
+
_mti_namespace.all.reject{|model| model.abstract_class }.each do |subclass|
|
50
|
+
key = :"#{subclass.name.demodulize.underscore}_entity"
|
51
|
+
has_one key, :class_name => subclass.name, :foreign_key => :id
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def multiple_table_inheritance_namespace(namespace)
|
56
|
+
mti_namespace(namespace)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Trax
|
2
|
+
module Model
|
3
|
+
module MTI
|
4
|
+
module Namespace
|
5
|
+
extend ::Trax::Core::EagerAutoloadNamespace
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :base_mti_model
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.base_model(model)
|
12
|
+
@base_mti_model = model
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.all
|
16
|
+
@all ||= base_mti_model.subclasses
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/trax/model/registry.rb
CHANGED
@@ -52,7 +52,7 @@ module Trax
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def self.uuid_map
|
55
|
-
|
55
|
+
models.values.reject{|model| model.try(:uuid_prefix) == nil }.inject(::Hashie::Mash.new) do |result, model|
|
56
56
|
result[model.uuid_prefix] = model
|
57
57
|
result
|
58
58
|
end
|
data/lib/trax_model.rb
CHANGED
data/lib/trax_model/version.rb
CHANGED
data/trax_model.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "trax_core"
|
21
|
+
spec.add_dependency "trax_core", "~> 0.0.3"
|
22
22
|
spec.add_dependency "default_value_for", "~> 3.0.0"
|
23
23
|
spec.add_development_dependency "activerecord"
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.6"
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trax_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Ayre
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
11
|
+
date: 2014-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trax_core
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.0.3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.0.3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: default_value_for
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -268,6 +268,10 @@ files:
|
|
268
268
|
- lib/trax/model/config.rb
|
269
269
|
- lib/trax/model/freezable.rb
|
270
270
|
- lib/trax/model/matchable.rb
|
271
|
+
- lib/trax/model/mti.rb
|
272
|
+
- lib/trax/model/mti/abstract.rb
|
273
|
+
- lib/trax/model/mti/entity.rb
|
274
|
+
- lib/trax/model/mti/namespace.rb
|
271
275
|
- lib/trax/model/registry.rb
|
272
276
|
- lib/trax/model/unique_id.rb
|
273
277
|
- lib/trax/model/uuid.rb
|
@@ -317,7 +321,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
317
321
|
version: '0'
|
318
322
|
requirements: []
|
319
323
|
rubyforge_project:
|
320
|
-
rubygems_version: 2.
|
324
|
+
rubygems_version: 2.4.3
|
321
325
|
signing_key:
|
322
326
|
specification_version: 4
|
323
327
|
summary: Higher level ActiveRecord models for rails
|