trax_model 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0ed46643e7e35aa4ab571dac149147ff661f740c
4
- data.tar.gz: 7beb964bc0b37e05220191499f043d38be298a64
3
+ metadata.gz: b684425923dcaa1272ed04a98ab652ecf61c56ca
4
+ data.tar.gz: 5c66900bde83b806346c7784ed378ed92ff0c50f
5
5
  SHA512:
6
- metadata.gz: 411e43d1938a872495eb850a65aae17e375ce5dccbb6f8f0dfda5e286893b3d2859d096b1e68e92cec5535f91ba166e6e18ac609d9560708c23a516116901bfe
7
- data.tar.gz: 70774f22c946132a1deeda5c510a05591d77aa90fdf6f9956f21b2f42f898e88b2632a572cfa37588f9bb52fe0bffb49d3225e333231eca2a7dba80887a43bd3
6
+ metadata.gz: ee0a7829a4bfe0098f47a5f2e8e8da1e6dd54abb10fc129f1addef047108a0f359396fe3a27b37bc7155c734667d9bc5ee68265af85adcefd96859108d8eb871
7
+ data.tar.gz: 0283a5f7de147bd1a5d51c38e925c3935696f2ea00ecc390e3735d249e48b92e7d3628fb6489c3483b7c0fdfe38ed95de64b83a00a28277b0c8b7c68e60c67f4
data/README.md CHANGED
@@ -163,6 +163,53 @@ The main disadvantages are:
163
163
  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)
164
164
  2. More difficult to setup since each child table needs its own table.
165
165
 
166
+ # STI
167
+
168
+ Trax also has a specialized blend of STI, where you place the union of attributes in the parent table, but for the child specific attributes, you create one separate table per subclass, i.e. given the following pseudo schema
169
+ ``` ruby
170
+ #(Note this may make more sense to be a has_one rather than a belongs_to, cant remember why I set it up that way)
171
+
172
+ Post
173
+ :type => string, :title => String, :attribute_set_id => (integer or uuid, this is required ATM.)
174
+
175
+ VideoPost
176
+ video_url => string
177
+ video_thumbnail => binary
178
+
179
+ TextPost
180
+ body => text
181
+ ```
182
+ You would have 2 additional models
183
+
184
+ 1. VideoPostAttributeSet
185
+ 2. TextPostAttributeSet
186
+
187
+ Which contain the type specific columns.
188
+
189
+ Then you just need to include
190
+
191
+ ``` ruby
192
+ class VideoPost < Post
193
+ include ::Trax::Model::STI::Attributes
194
+
195
+ sti_attribute :video_url, :video_thumbnail
196
+ end
197
+ ```
198
+
199
+ STI Attribute will set up the delegation to the attribute_set model, so now you can do
200
+
201
+ ``` ruby
202
+ VideoPost.new(:video_url => "http://whatever.com")
203
+ ```
204
+
205
+ ETC..
206
+
207
+ AttributeSet model will be built automatically if it does not exist, and delegated to accordingly.
208
+
209
+ The idea is to hide the complexity of dealing with the attribute_set table, and do as much as possible in the main model, as its really just an extension of itself.
210
+
211
+ If you need to override one of the attribute_sets methods, try super! as that will delegate to it and call super on the attribute set model.
212
+
166
213
 
167
214
  ## Installation
168
215
 
@@ -20,18 +20,10 @@
20
20
  module Trax
21
21
  module Model
22
22
  module Enum
23
- extend ::ActiveSupport::Concern
23
+ include ::Trax::Model::Mixin
24
24
 
25
25
  module ClassMethods
26
- def as_enum(enum_name, args, options = {})
27
- options.assert_valid_keys(:prefix, :source, :message)
28
- options[:message] ||= "Is not a valid value for #{enum_name}"
29
- options[:prefix] ||= true
30
- options[:source] ||= enum_name
31
- enum_values = args.is_a?(Hash) ? args.keys : args
32
- validation_options = { :in => enum_values, :message => options.extract!(:message) }
33
-
34
- self.validates_inclusion_of(enum_name, validation_options)
26
+ def define_scopes_for_trax_enum(enum_name)
35
27
  scope_method_name = :"by_#{enum_name}"
36
28
  scope_not_method_name = :"by_#{enum_name}_not"
37
29
 
@@ -43,8 +35,28 @@ module Trax
43
35
  enum_hash = self.__send__("#{enum_name}".pluralize).hash
44
36
  where.not(enum_name => enum_hash.with_indifferent_access.slice(*values.flatten.compact.uniq).values)
45
37
  }
38
+ end
39
+
40
+ #todo: ew need to clean this up
41
+ def as_enum(enum_name, enum_mapping, options = {})
42
+ enum_values = enum_mapping.is_a?(Hash) ? enum_mapping.keys : enum_mapping
43
+ options.assert_valid_keys(:prefix, :source, :message, :default, :validate)
44
+
45
+ options[:message] ||= "Invalid value selected for #{enum_name}"
46
+ options[:prefix] ||= true
47
+ options[:source] ||= enum_name
48
+ default_value = options.extract!(:default)[:default] if options.key?(:default)
49
+
50
+ validation_options = { :in => enum_values, :message => options.extract!(:message)[:message] }
51
+
52
+ self.validates_inclusion_of(enum_name, validation_options) unless options.key?(:validate) && !options[:validate]
53
+ options.delete(:validate) if options.key?(:validate)
54
+
55
+ define_scopes_for_trax_enum(enum_name)
56
+
57
+ self.default_value_for(enum_name) { default_value } if default_value
46
58
 
47
- super(enum_name, args, options)
59
+ super(enum_name, enum_mapping, options)
48
60
  end
49
61
  end
50
62
  end
@@ -22,6 +22,12 @@ module Trax
22
22
  "following prefix was already registered"
23
23
  ]
24
24
  end
25
+
26
+ class STIAttributeNotFound < ::Trax::Model::Errors::Base
27
+ MESSAGE = [
28
+ "STI Attribute was not found for attribute"
29
+ ]
30
+ end
25
31
  end
26
32
  end
27
33
  end
@@ -1,7 +1,7 @@
1
1
  module Trax
2
2
  module Model
3
3
  module Freezable
4
- extend ::ActiveSupport::Concern
4
+ include ::Trax::Model::Mixin
5
5
 
6
6
  included do
7
7
  class_attribute :freezable_fields
@@ -11,7 +11,6 @@ module Trax
11
11
  module ClassMethods
12
12
  def freezable_by_enum(options = {})
13
13
  freezable_fields.merge!(options)
14
-
15
14
  define_frozen_validators_for_enum(options)
16
15
  end
17
16
 
@@ -26,6 +25,5 @@ module Trax
26
25
  end
27
26
  end
28
27
  end
29
-
30
28
  end
31
29
  end
@@ -0,0 +1,13 @@
1
+ module Trax
2
+ module Model
3
+ module Mixin
4
+ extend ::ActiveSupport::Concern
5
+
6
+ included do
7
+ self.extend(::ActiveSupport::Concern)
8
+
9
+ ::Trax::Model.register_mixin(self)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -8,7 +8,9 @@ module Trax
8
8
  class_attribute :mti_config
9
9
 
10
10
  self.abstract_class = true
11
- self.mti_config = ::Hashie::Mash.new(:foreign_key => :id)
11
+ self.mti_config = ::ActiveSupport::HashWithIndifferentAccess.new({
12
+ :foreign_key => :id
13
+ })
12
14
 
13
15
  scope :records, lambda{
14
16
  map(&:entity)
@@ -50,6 +52,7 @@ module Trax
50
52
 
51
53
  def entity_model(options)
52
54
  valid_options = options.assert_valid_keys(:class_name, :foreign_key)
55
+
53
56
  mti_config.merge!(valid_options)
54
57
 
55
58
  self.has_one(:entity, mti_config.symbolize_keys)
@@ -0,0 +1,52 @@
1
+ module Trax
2
+ module Model
3
+ module Restorable
4
+ include ::Trax::Model::Mixin
5
+
6
+ included do
7
+ class_attribute :_restorable_config
8
+
9
+ alias_method :destroy!, :destroy
10
+
11
+ self._restorable_config = {
12
+ :field => :is_deleted,
13
+ :timestamp_field => :deleted_at,
14
+ :hide_deleted => true
15
+ }
16
+ end
17
+
18
+ module ClassMethods
19
+ def setup_restorable!
20
+ self.class_eval do
21
+ if(self._restorable_config[:hide_deleted])
22
+ default_scope { by_not_deleted }
23
+ end
24
+
25
+ ### Clear default deleted scope ###
26
+ scope :by_is_deleted, lambda { |*|
27
+ unscope(:where => self._restorable_config[:field]).where(self._restorable_config[:field] => true)
28
+ }
29
+ scope :by_not_deleted, lambda { |*|
30
+ where(self._restorable_config[:field] => false)
31
+ }
32
+
33
+ default_value_for(self._restorable_config[:field]) { false }
34
+ end
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ self.update_attributes(self._restorable_config[:field] => true, self._restorable_config[:timestamp_field] => ::DateTime.now)
40
+ end
41
+
42
+ def restore
43
+ self.update_attributes(self._restorable_config[:field] => false, self._restorable_config[:timestamp_field] => ::DateTime.now)
44
+ end
45
+
46
+ def self.apply_mixin(target, options)
47
+ target._restorable_config.merge!(options)
48
+ target.setup_restorable!
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,94 @@
1
+ module Trax
2
+ module Model
3
+ module STI
4
+ module Attributes
5
+ extend ::ActiveSupport::Concern
6
+
7
+ #not configurable atm as you can see below
8
+
9
+ included do
10
+ class_attribute :_attribute_set_column,
11
+ :_attribute_set_class_name,
12
+ :_attribute_set_relation_name,
13
+ :_sti_attributes
14
+
15
+ self._sti_attributes = []
16
+
17
+ self._attribute_set_relation_name = :attribute_set
18
+ self._attribute_set_column = :attribute_set_id
19
+ self._attribute_set_class_name = "#{name}AttributeSet"
20
+
21
+ self.belongs_to(self._attribute_set_relation_name, :class_name => self._attribute_set_class_name, :validate => true)
22
+ self.validates(self._attribute_set_relation_name, :presence => true)
23
+
24
+ self.before_create(:attribute_set)
25
+ end
26
+
27
+ def attributes
28
+ super.merge!(sti_attributes)
29
+ end
30
+
31
+ def attribute_set
32
+ build_attribute_set if super.nil?
33
+
34
+ super
35
+ end
36
+
37
+ def sti_attributes
38
+ sti_attribute_hash = self.class._sti_attributes.inject({}) do |result, attribute|
39
+ result["#{attribute}"] = __send__(attribute)
40
+ result
41
+ end
42
+
43
+ sti_attribute_hash
44
+ end
45
+
46
+ def super!(*args)
47
+ method_caller = caller_locations(1,1)[0].label
48
+ attribute_set.send(method_caller, *args)
49
+ end
50
+
51
+ module ClassMethods
52
+ def sti_attribute(*args)
53
+ options = args.extract_options!
54
+
55
+ args.each do |attribute_name|
56
+ return unless self._attribute_set_class_name.constantize.table_exists?
57
+ raise ::Trax::Model::Errors::STIAttributeNotFound unless self._attribute_set_class_name.constantize.column_names.include?("#{attribute_name}")
58
+
59
+ self._sti_attributes << attribute_name
60
+
61
+ self.delegate(attribute_name, :to => :attribute_set)
62
+ self.delegate("#{attribute_name}=", :to => :attribute_set) unless options.key?(:writer) && options[:writer] == false
63
+ end
64
+ end
65
+
66
+ def validates_uniqueness_of!(*args)
67
+ options = args.extract_options!
68
+
69
+ args.each do |arg|
70
+ validation_method_name = :"validate_#{arg.to_s}"
71
+
72
+ self.send(:define_method, :"validate_#{arg.to_s}") do |*_args|
73
+ where_scope_hash = {}
74
+ field_value = self.__send__(arg)
75
+ where_scope_hash[arg] = field_value
76
+
77
+ where_scope = self._attribute_set_class_name.constantize.where(where_scope_hash).all
78
+
79
+ options[:scope].each do |field|
80
+ scoped_field_value = self.__send__(field)
81
+ where_scope.merge(self.class.where({field => scoped_field_value}))
82
+ end if options.has_key?(:scope)
83
+
84
+ errors.add(arg, "has already been taken") if where_scope.limit(1).any?
85
+ end
86
+
87
+ self.validate(validation_method_name)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,9 @@
1
+ module Trax
2
+ module Model
3
+ module STI
4
+ extend ::ActiveSupport::Autoload
5
+
6
+ autoload :Attributes
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,7 @@
1
1
  module Trax
2
2
  module Model
3
3
  module UniqueId
4
- extend ::ActiveSupport::Concern
4
+ include ::Trax::Model::Mixin
5
5
 
6
6
  included do
7
7
  #grab prefix from uuid registry if using that
data/lib/trax/model.rb CHANGED
@@ -4,6 +4,7 @@ require 'hashie/dash'
4
4
  require 'hashie/mash'
5
5
  require 'simple_enum'
6
6
  require_relative './string'
7
+ # require_relative './enum'
7
8
  require_relative './validators/email_validator'
8
9
  require_relative './validators/frozen_validator'
9
10
  require_relative './validators/future_validator'
@@ -24,12 +25,35 @@ module Trax
24
25
  autoload :UUIDPrefix
25
26
  autoload :UniqueId
26
27
  autoload :Matchable
28
+ autoload :Mixin
27
29
  autoload :MTI
30
+ autoload :Restorable
31
+ autoload :STI
28
32
  autoload :Validators
29
33
 
30
34
  include ::Trax::Model::Matchable
31
35
  include ::ActiveModel::Dirty
32
36
 
37
+ class << self
38
+ attr_accessor :mixin_registry
39
+ end
40
+
41
+ @mixin_registry = {}
42
+
43
+ def self.register_mixin(mixin_klass)
44
+ mixin_key = mixin_klass.name.demodulize.underscore.to_sym
45
+ mixin_registry[mixin_key] = mixin_klass
46
+ end
47
+
48
+ def self.eager_autoload_mixins!
49
+ ::Trax::Model::Enum
50
+ ::Trax::Model::Freezable
51
+ ::Trax::Model::Restorable
52
+ ::Trax::Model::UniqueId
53
+ end
54
+
55
+ eager_autoload_mixins!
56
+
33
57
  included do
34
58
  class_attribute :trax_defaults
35
59
 
@@ -48,6 +72,30 @@ module Trax
48
72
  end
49
73
  end
50
74
 
75
+ def mixin(key, options = {})
76
+ mixin_klass = ::Trax::Model.mixin_registry[key]
77
+
78
+ self.class_eval do
79
+ include(mixin_klass) unless self.ancestors.include?(mixin_klass)
80
+
81
+ if(options.is_a?(Hash) && !options.blank?)
82
+ mixin_klass.apply_mixin(self, options) if mixin_klass.respond_to?(:apply_mixin)
83
+ end
84
+ end
85
+ end
86
+
87
+ def mixins(*args)
88
+ options = args.extract_options!
89
+
90
+ if(!options.blank?)
91
+ options.each_pair do |key, val|
92
+ self.mixin(key, val)
93
+ end
94
+ else
95
+ args.map{ |key| mixin(key) }
96
+ end
97
+ end
98
+
51
99
  def register_trax_models(*models)
52
100
  models.each do |model|
53
101
  register_trax_model(model)
@@ -1,3 +1,3 @@
1
1
  module TraxModel
2
- VERSION = '0.0.8'
2
+ VERSION = '0.0.9'
3
3
  end
@@ -28,6 +28,8 @@ ActiveRecord::Schema.define(:version => 1) do
28
28
  t.text "body"
29
29
  t.integer "status"
30
30
  t.string "uuid"
31
+ t.boolean "deleted"
32
+ t.datetime "deleted_at"
31
33
  t.datetime "deliver_at"
32
34
  t.datetime "created_at", :null => false
33
35
  t.datetime "updated_at", :null => false
@@ -55,6 +57,22 @@ ActiveRecord::Schema.define(:version => 1) do
55
57
  t.datetime "created_at", :null => false
56
58
  t.datetime "updated_at", :null => false
57
59
  end
60
+
61
+ create_table "staplers", :force => true do |t|
62
+ t.string "name"
63
+ t.string "type"
64
+ t.integer "attribute_set_id"
65
+ t.datetime "created_at", :null => false
66
+ t.datetime "updated_at", :null => false
67
+ end
68
+
69
+ create_table "swingline_stapler_attribute_sets", :force => true do |t|
70
+ t.float "speed"
71
+ t.string "owner"
72
+
73
+ t.datetime "created_at", :null => false
74
+ t.datetime "updated_at", :null => false
75
+ end
58
76
  end
59
77
 
60
78
  class Product < ::ActiveRecord::Base
@@ -76,10 +94,12 @@ end
76
94
 
77
95
  class Message < ::ActiveRecord::Base
78
96
  include ::Trax::Model
79
- include ::Trax::Model::Freezable
80
97
 
81
98
  defaults :uuid_prefix => "3a", :uuid_column => "uuid"
82
99
 
100
+ mixins :freezable => true,
101
+ :restorable => { :field => :deleted }
102
+
83
103
  enum :status => [:queued, :scheduled, :delivered, :failed_delivery]
84
104
 
85
105
  default_value_for :status do
@@ -88,7 +108,7 @@ class Message < ::ActiveRecord::Base
88
108
 
89
109
  validates :deliver_at, :future => true, :allow_nil => true
90
110
 
91
- freezable_by_enum :status => [:delivered, :failed_delivery]
111
+ freezable_by_enum :status => [ :delivered, :failed_delivery ]
92
112
  end
93
113
 
94
114
  class Thing < ::ActiveRecord::Base
@@ -102,3 +122,14 @@ class Person < ::ActiveRecord::Base
102
122
 
103
123
  defaults :uuid_column => "uuid"
104
124
  end
125
+
126
+ class Stapler < ::ActiveRecord::Base
127
+ include ::Trax::Model
128
+ end
129
+
130
+ class SwinglineStapler < ::Stapler
131
+ include ::Trax::Model::STI::Attributes
132
+ end
133
+
134
+ class SwinglineStaplerAttributeSet < ::ActiveRecord::Base
135
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Trax::Model::Restorable do
4
+ subject{ ::Message.create(:title => "Whatever") }
5
+
6
+ its(:deleted) { should be false }
7
+
8
+ context "when destroyed" do
9
+ it "should soft delete" do
10
+ subject.destroy
11
+ subject.deleted.should be true
12
+ end
13
+
14
+ it "should be restorable" do
15
+ subject.destroy
16
+ subject.restore
17
+ subject.deleted.should be false
18
+ end
19
+ end
20
+
21
+ context "scopes" do
22
+ subject{ ::Message.create(:title => "My Message") }
23
+
24
+ context ".default_scope" do
25
+ it { Message.all.where_values_hash["deleted"].should eq false }
26
+
27
+ it do
28
+ subject
29
+ Message.all.pluck(:id).should include(subject.id)
30
+ end
31
+
32
+ it do
33
+ subject.destroy
34
+ Message.all.pluck(:id).should_not include(subject.id)
35
+ end
36
+ end
37
+
38
+ it ".by_is_deleted" do
39
+ subject.destroy
40
+ binding.pry
41
+ Message.by_is_deleted.pluck(:id).should include(subject.id)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Trax::Model::STI::Attributes do
4
+
5
+ before do
6
+ SwinglineStapler.sti_attribute(:owner)
7
+ end
8
+
9
+ context "delegation" do
10
+ subject{ ::SwinglineStapler.new(:owner => "Milton Waddums") }
11
+
12
+ its(:owner) { should eq "Milton Waddums" }
13
+ end
14
+
15
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trax_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
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-18 00:00:00.000000000 Z
11
+ date: 2015-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trax_core
@@ -284,11 +284,15 @@ files:
284
284
  - lib/trax/model/errors.rb
285
285
  - lib/trax/model/freezable.rb
286
286
  - lib/trax/model/matchable.rb
287
+ - lib/trax/model/mixin.rb
287
288
  - lib/trax/model/mti.rb
288
289
  - lib/trax/model/mti/abstract.rb
289
290
  - lib/trax/model/mti/entity.rb
290
291
  - lib/trax/model/mti/namespace.rb
291
292
  - lib/trax/model/registry.rb
293
+ - lib/trax/model/restorable.rb
294
+ - lib/trax/model/sti.rb
295
+ - lib/trax/model/sti/attributes.rb
292
296
  - lib/trax/model/unique_id.rb
293
297
  - lib/trax/model/uuid.rb
294
298
  - lib/trax/model/uuid_prefix.rb
@@ -308,6 +312,8 @@ files:
308
312
  - spec/trax/model/freezable_spec.rb
309
313
  - spec/trax/model/matchable_spec.rb
310
314
  - spec/trax/model/registry_spec.rb
315
+ - spec/trax/model/restorable_spec.rb
316
+ - spec/trax/model/sti/attributes_spec.rb
311
317
  - spec/trax/model/unique_id_spec.rb
312
318
  - spec/trax/model/uuid_prefix_spec.rb
313
319
  - spec/trax/model/uuid_spec.rb
@@ -338,7 +344,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
338
344
  version: '0'
339
345
  requirements: []
340
346
  rubyforge_project:
341
- rubygems_version: 2.2.2
347
+ rubygems_version: 2.4.5
342
348
  signing_key:
343
349
  specification_version: 4
344
350
  summary: Higher level ActiveRecord models for rails
@@ -350,6 +356,8 @@ test_files:
350
356
  - spec/trax/model/freezable_spec.rb
351
357
  - spec/trax/model/matchable_spec.rb
352
358
  - spec/trax/model/registry_spec.rb
359
+ - spec/trax/model/restorable_spec.rb
360
+ - spec/trax/model/sti/attributes_spec.rb
353
361
  - spec/trax/model/unique_id_spec.rb
354
362
  - spec/trax/model/uuid_prefix_spec.rb
355
363
  - spec/trax/model/uuid_spec.rb