trax_model 0.0.97 → 0.0.98
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Guardfile +1 -1
- data/lib/trax/model.rb +4 -1
- data/lib/trax/model/attributes/types/enum.rb +1 -1
- data/lib/trax/model/attributes/types/struct.rb +1 -1
- data/lib/trax/model/mixins.rb +1 -0
- data/lib/trax/model/mixins/field_scopes.rb +46 -12
- data/lib/trax/model/mixins/sti_enum.rb +51 -0
- data/lib/trax/model/string_extensions.rb +11 -0
- data/lib/trax/model/struct_extensions.rb +59 -0
- data/lib/trax/model/uuid.rb +21 -2
- data/lib/trax_model/version.rb +1 -1
- data/spec/db/schema/default_tables.rb +9 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/models.rb +36 -3
- data/spec/support/pg/models.rb +20 -8
- data/spec/trax/model/attributes/types/struct_spec.rb +81 -1
- data/spec/trax/model/mixins/field_scopes_spec.rb +42 -2
- data/spec/trax/model/mixins/sti_enum_spec.rb +7 -0
- data/spec/trax/string_spec.rb +3 -1
- data/trax_model.gemspec +1 -1
- metadata +8 -5
- data/lib/trax/string.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a56cf1df33c14218b82e1c7b26c649c52adb18df
|
4
|
+
data.tar.gz: e12109ca5ea6fb9abccb869c7c67d87431614f9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc8eb0602e23a03e13a87daf5420f3ea51f1dab49708cee5128c242e175609f795a852185f60c2b26f8a250d42c7a2873a75a8b9c1d8007111cd5afcb6fe338a
|
7
|
+
data.tar.gz: ced26e12c60dcff2b03ccb64bfb1c0bae10e526eb9cb2e9748be3a528e67db668634f47d1ac5f6f4e5b6feca75cf39e1390677c415ba7f8b3365ff3c13fe0708
|
data/Guardfile
CHANGED
@@ -17,7 +17,7 @@ end
|
|
17
17
|
# * 'just' rspec: 'rspec'
|
18
18
|
guard :rspec, cmd: 'bundle exec rspec' do
|
19
19
|
watch(%r{^spec/.+_spec\.rb$})
|
20
|
-
watch(%r{^lib/(.+)\.rb$}) { |m| "spec
|
20
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
21
21
|
watch('spec/spec_helper.rb') { "spec" }
|
22
22
|
|
23
23
|
# Rails example
|
data/lib/trax/model.rb
CHANGED
@@ -5,7 +5,6 @@ require 'hashie/mash'
|
|
5
5
|
require 'hashie/trash'
|
6
6
|
require 'hashie/extensions/dash/indifferent_access'
|
7
7
|
require 'simple_enum'
|
8
|
-
require_relative './string'
|
9
8
|
require_relative './validators/boolean_validator'
|
10
9
|
require_relative './validators/email_validator'
|
11
10
|
require_relative './validators/frozen_validator'
|
@@ -39,6 +38,7 @@ module Trax
|
|
39
38
|
autoload :Restorable
|
40
39
|
autoload :Railtie
|
41
40
|
autoload :STI
|
41
|
+
autoload :StringExtensions
|
42
42
|
autoload :Struct
|
43
43
|
autoload :StructExtensions
|
44
44
|
autoload :Validators
|
@@ -85,6 +85,7 @@ module Trax
|
|
85
85
|
::Trax::Model::Mixins::FieldScopes
|
86
86
|
::Trax::Model::Mixins::IdScopes
|
87
87
|
::Trax::Model::Mixins::SortByScopes
|
88
|
+
::Trax::Model::Mixins::StiEnum
|
88
89
|
end
|
89
90
|
|
90
91
|
eager_autoload_mixins!
|
@@ -156,6 +157,8 @@ module Trax
|
|
156
157
|
end
|
157
158
|
end
|
158
159
|
|
160
|
+
::String.include(::Trax::Model::StringExtensions)
|
161
|
+
|
159
162
|
::ActiveSupport.run_load_hooks(:trax_model, self)
|
160
163
|
end
|
161
164
|
end
|
@@ -10,7 +10,7 @@ module Trax
|
|
10
10
|
klass_name = "#{klass.fields_module.name.underscore}/#{attribute_name}".camelize
|
11
11
|
|
12
12
|
attribute_klass = if options.key?(:extend)
|
13
|
-
_klass_prototype = options[:extend].
|
13
|
+
_klass_prototype = options[:extend].is_a?(::String) ? options[:extend].safe_constantize : options[:extend]
|
14
14
|
_klass = ::Trax::Core::NamedClass.new(klass_name, _klass_prototype, :parent_definition => klass, &block)
|
15
15
|
_klass
|
16
16
|
else
|
@@ -8,7 +8,7 @@ module Trax
|
|
8
8
|
def self.define_attribute(klass, attribute_name, **options, &block)
|
9
9
|
klass_name = "#{klass.fields_module.name.underscore}/#{attribute_name}".camelize
|
10
10
|
attribute_klass = if options.key?(:extend)
|
11
|
-
_klass_prototype = options[:extend].
|
11
|
+
_klass_prototype = options[:extend].is_a?(::String) ? options[:extend].safe_constantize : options[:extend]
|
12
12
|
_klass = ::Trax::Core::NamedClass.new(klass_name, _klass_prototype, :parent_definition => klass, &block)
|
13
13
|
_klass.include(::Trax::Model::StructExtensions)
|
14
14
|
_klass
|
data/lib/trax/model/mixins.rb
CHANGED
@@ -14,13 +14,11 @@ module Trax
|
|
14
14
|
case field_scope_options[:type]
|
15
15
|
when :where
|
16
16
|
define_where_scope_for_field(field_scope_name, **field_scope_options)
|
17
|
-
when :
|
17
|
+
when :where_lower
|
18
|
+
define_where_lower_scope_for_field(field_scope_name, **field_scope_options)
|
19
|
+
when :match, :matching
|
18
20
|
define_matching_scope_for_field(field_scope_name, **field_scope_options)
|
19
|
-
when :
|
20
|
-
define_matching_scope_for_field(field_scope_name, **field_scope_options)
|
21
|
-
when :not
|
22
|
-
define_where_not_scope_for_field(field_scope_name, **field_scope_options)
|
23
|
-
when :where_not
|
21
|
+
when :where_not, :not
|
24
22
|
define_where_not_scope_for_field(field_scope_name, **field_scope_options)
|
25
23
|
else
|
26
24
|
define_where_scope_for_field(field_scope_name, **field_scope_options)
|
@@ -32,8 +30,32 @@ module Trax
|
|
32
30
|
private
|
33
31
|
def define_where_scope_for_field(field_scope_name, **options)
|
34
32
|
scope field_scope_name, lambda{ |*_values|
|
35
|
-
_values.
|
36
|
-
|
33
|
+
_relation = if _values.first.is_a?(::ActiveRecord::Relation)
|
34
|
+
where(options[:field] => _values.first)
|
35
|
+
else
|
36
|
+
_values.flat_compact_uniq!
|
37
|
+
where(options[:field] => _values)
|
38
|
+
end
|
39
|
+
|
40
|
+
_relation
|
41
|
+
}
|
42
|
+
|
43
|
+
# Alias scope names with pluralized versions, i.e. by_id also => by_ids
|
44
|
+
singleton_class.__send__(:alias_method, :"#{field_scope_name.to_s.pluralize}", field_scope_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def define_where_lower_scope_for_field(field_scope_name, **options)
|
48
|
+
scope field_scope_name, lambda{ |*_values|
|
49
|
+
_query = "lower(#{options[:field]}) in (?)"
|
50
|
+
|
51
|
+
_relation = if _values.first.is_a?(::ActiveRecord::Relation)
|
52
|
+
where(_query, _values.first)
|
53
|
+
else
|
54
|
+
_values.map!(&:downcase)
|
55
|
+
where(_query, _values)
|
56
|
+
end
|
57
|
+
|
58
|
+
_relation
|
37
59
|
}
|
38
60
|
|
39
61
|
# Alias scope names with pluralized versions, i.e. by_id also => by_ids
|
@@ -42,15 +64,27 @@ module Trax
|
|
42
64
|
|
43
65
|
def define_where_not_scope_for_field(field_scope_name, **options)
|
44
66
|
scope field_scope_name, lambda{ |*_values|
|
45
|
-
_values.
|
46
|
-
|
67
|
+
_relation = if _values.first.is_a?(::ActiveRecord::Relation)
|
68
|
+
where.not(options[:field] => _values.first)
|
69
|
+
else
|
70
|
+
_values.flat_compact_uniq!
|
71
|
+
where.not(options[:field] => _values)
|
72
|
+
end
|
73
|
+
|
74
|
+
_relation
|
47
75
|
}
|
48
76
|
end
|
49
77
|
|
50
78
|
def define_matching_scope_for_field(field_scope_name, **options)
|
51
79
|
scope field_scope_name, lambda{ |*_values|
|
52
|
-
_values.
|
53
|
-
|
80
|
+
_relation = if _values.first.is_a?(::ActiveRecord::Relation)
|
81
|
+
matching(options[:field] => _values.first)
|
82
|
+
else
|
83
|
+
_values.flat_compact_uniq!
|
84
|
+
matching(options[:field] => _values)
|
85
|
+
end
|
86
|
+
|
87
|
+
_relation
|
54
88
|
}
|
55
89
|
end
|
56
90
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
module Trax
|
3
|
+
module Model
|
4
|
+
module Mixins
|
5
|
+
module StiEnum
|
6
|
+
extend ::Trax::Model::Mixin
|
7
|
+
|
8
|
+
mixed_in do |**options|
|
9
|
+
default_options = { :type_column => :type, :enum_column => :kind }
|
10
|
+
|
11
|
+
options = default_options.merge!(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
included do
|
15
|
+
after_initialize :set_type_from_kind, :unless => :type?, :if => :kind?
|
16
|
+
after_initialize :set_kind_from_type, :unless => :kind?, :if => :type?
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_type_from_kind
|
20
|
+
self[:type] = self.class.kind_to_type_mapping[self[:kind]] if !self[:type]
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_kind_from_type
|
24
|
+
self[:kind] = self.class.type_to_kind_mapping[self[:type]] if !self[:kind]
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
def subclass_from_attributes?(attrs)
|
29
|
+
_attrs = attrs.with_indifferent_access if attrs
|
30
|
+
attrs[:type] = kind_to_type_mapping[_attrs["kind"]] if attrs && !_attrs.key?("type") && _attrs.key?("kind")
|
31
|
+
super(attrs)
|
32
|
+
end
|
33
|
+
|
34
|
+
def type_to_kind_mapping
|
35
|
+
@type_to_kind_mapping ||= fields[:kind].choices.each_with_object({}) do |choice, result|
|
36
|
+
result[choice.attributes[:type]] = choice.to_s
|
37
|
+
result
|
38
|
+
end.with_indifferent_access
|
39
|
+
end
|
40
|
+
|
41
|
+
def kind_to_type_mapping
|
42
|
+
@kind_to_type_mapping ||= fields[:kind].choices.each_with_object({}) do |choice, result|
|
43
|
+
result[choice.to_s] = choice.attributes[:type]
|
44
|
+
result
|
45
|
+
end.with_indifferent_access
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -40,11 +40,70 @@ module Trax
|
|
40
40
|
define_where_scopes_for_boolean_property(attribute_name, attribute_klass, **options)
|
41
41
|
when :enum
|
42
42
|
define_scopes_for_enum(attribute_name, attribute_klass, **options)
|
43
|
+
when :array
|
44
|
+
define_scopes_for_array(attribute_name, attribute_klass, **options)
|
45
|
+
when :integer
|
46
|
+
define_scopes_for_numeric(attribute_name, attribute_klass, **options)
|
47
|
+
when :time
|
48
|
+
define_scopes_for_time(attribute_name, attribute_klass, **options)
|
43
49
|
else
|
44
50
|
define_where_scopes_for_property(attribute_name, attribute_klass, **options)
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
54
|
+
def define_scopes_for_array(attribute_name, property_klass, as:nil)
|
55
|
+
return unless has_active_record_ancestry?(property_klass)
|
56
|
+
|
57
|
+
model_class = model_class_for_property(property_klass)
|
58
|
+
field_name = property_klass.parent_definition.name.demodulize.underscore
|
59
|
+
attribute_name = property_klass.name.demodulize.underscore
|
60
|
+
scope_name = as || :"by_#{field_name}_#{attribute_name}"
|
61
|
+
|
62
|
+
model_class.scope(scope_name, lambda{ |*_scope_values|
|
63
|
+
_scope_values.flat_compact_uniq!
|
64
|
+
model_class.where("#{field_name} -> '#{attribute_name}' ?| array[:values]", :values => _scope_values)
|
65
|
+
})
|
66
|
+
end
|
67
|
+
|
68
|
+
def define_scopes_for_numeric(attribute_name, property_klass, as:nil)
|
69
|
+
return unless has_active_record_ancestry?(property_klass)
|
70
|
+
|
71
|
+
model_class = model_class_for_property(property_klass)
|
72
|
+
field_name = property_klass.parent_definition.name.demodulize.underscore
|
73
|
+
attribute_name = property_klass.name.demodulize.underscore
|
74
|
+
cast_type = property_klass.type
|
75
|
+
|
76
|
+
{ :gt => '>', :gte => '>=', :lt => '<', :lte => '<=', :eq => '='}.each_pair do |k, operator|
|
77
|
+
scope_name = as ? :"#{as}_#{k}" : :"by_#{field_name}_#{attribute_name}_#{k}"
|
78
|
+
|
79
|
+
model_class.scope(scope_name, lambda{ |*_scope_values|
|
80
|
+
_scope_values.flat_compact_uniq!
|
81
|
+
model_class.where("(#{field_name} ->> '#{attribute_name}')::#{cast_type} #{operator} ?", _scope_values)
|
82
|
+
})
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def define_scopes_for_time(attribute_name, property_klass, as:nil)
|
87
|
+
return unless has_active_record_ancestry?(property_klass)
|
88
|
+
|
89
|
+
model_class = model_class_for_property(property_klass)
|
90
|
+
field_name = property_klass.parent_definition.name.demodulize.underscore
|
91
|
+
attribute_name = property_klass.name.demodulize.underscore
|
92
|
+
cast_type = 'timestamp'
|
93
|
+
|
94
|
+
{ :gt => '>', :lt => '<'}.each_pair do |k, operator|
|
95
|
+
scope_prefix = as ? as : :"by_#{field_name}_#{attribute_name}"
|
96
|
+
scope_name = "#{scope_prefix}_#{k}"
|
97
|
+
scope_alias = "#{scope_prefix}_#{{:gt => 'after', :lt => 'before' }[k]}"
|
98
|
+
|
99
|
+
model_class.scope(scope_name, lambda{ |*_scope_values|
|
100
|
+
_scope_values.flat_compact_uniq!
|
101
|
+
model_class.where("(#{field_name} ->> '#{attribute_name}')::#{cast_type} #{operator} ?", _scope_values)
|
102
|
+
})
|
103
|
+
model_class.singleton_class.__send__("alias_method", scope_alias.to_sym, scope_name)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
48
107
|
#this only supports properties 1 level deep, but works beautifully
|
49
108
|
#I.E. for this structure
|
50
109
|
# define_attributes do
|
data/lib/trax/model/uuid.rb
CHANGED
@@ -2,16 +2,27 @@ module Trax
|
|
2
2
|
module Model
|
3
3
|
class UUID < String
|
4
4
|
class_attribute :prefix_map
|
5
|
-
|
6
5
|
self.prefix_map = ::Hashie::Mash.new
|
7
6
|
|
7
|
+
def self.===(val)
|
8
|
+
return false unless (val.is_a?(::Trax::Model::UUID) || val.is_a?(::String)) && val.length == 36
|
9
|
+
return true if val.is_a?(::Trax::Model::UUID)
|
10
|
+
|
11
|
+
#i.e. if we have 2 and 3 digit lengths, for value 'ABCDE' return ['AB', 'ABC']
|
12
|
+
value_samples = prefix_lengths.map do |limit|
|
13
|
+
val[0..(limit-1)]
|
14
|
+
end
|
15
|
+
|
16
|
+
return value_samples.any?{|sample| prefixes.include?(sample) }
|
17
|
+
end
|
18
|
+
|
8
19
|
def self.klass_prefix_map
|
9
20
|
prefix_map.invert
|
10
21
|
end
|
11
22
|
|
12
23
|
def self.generate(prefix = nil)
|
13
24
|
uuid = ::SecureRandom.uuid
|
14
|
-
uuid[0..1] = prefix if prefix
|
25
|
+
uuid[0..(prefix.length-1)] = prefix if prefix
|
15
26
|
uuid
|
16
27
|
end
|
17
28
|
|
@@ -23,6 +34,14 @@ module Trax
|
|
23
34
|
prefix_map[:"#{prefix_value}"] = klass
|
24
35
|
end
|
25
36
|
|
37
|
+
def self.prefixes
|
38
|
+
@prefixes ||= ::Trax::Model::Registry.uuid_map.keys
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.prefix_lengths
|
42
|
+
@prefix_lengths ||= prefixes.map(&:length).uniq
|
43
|
+
end
|
44
|
+
|
26
45
|
def self.register(&block)
|
27
46
|
instance_exec(&block)
|
28
47
|
end
|
data/lib/trax_model/version.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
DEFAULT_TABLES = Proc.new do
|
2
|
+
create_table "vehicles", :force => true do |t|
|
3
|
+
t.string "type"
|
4
|
+
t.integer "kind"
|
5
|
+
t.integer "make"
|
6
|
+
t.integer "model"
|
7
|
+
t.string "uuid"
|
8
|
+
t.datetime "created_at", :null => false
|
9
|
+
t.datetime "updated_at", :null => false
|
10
|
+
end
|
2
11
|
create_table "products", :force => true do |t|
|
3
12
|
t.string "name"
|
4
13
|
t.integer "category_id"
|
data/spec/spec_helper.rb
CHANGED
@@ -14,7 +14,9 @@ ENV["DB"] ||= "sqllite"
|
|
14
14
|
ENV["DB"] = "postgres" if ENV["DB"] == "pg" || ENV["pg"] == "true"
|
15
15
|
|
16
16
|
RSpec.configure do |config|
|
17
|
+
config.filter_run :focus
|
17
18
|
config.filter_run_excluding :postgres => true unless ENV["DB"] == "postgres"
|
19
|
+
config.run_all_when_everything_filtered = true
|
18
20
|
|
19
21
|
config.before(:suite) do
|
20
22
|
db_config = ::YAML::load(::File.open("#{File.dirname(__FILE__)}/db/database.yml"))
|
data/spec/support/models.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_record'
|
2
|
+
require 'trax_core'
|
2
3
|
|
3
4
|
::ActiveRecord::Schema.define(:version => 1) do
|
4
5
|
require_relative '../db/schema/default_tables'
|
@@ -13,6 +14,15 @@ end
|
|
13
14
|
if ENV["DB"] == "postgres"
|
14
15
|
require_relative 'pg/models'
|
15
16
|
end
|
17
|
+
#
|
18
|
+
# class Blueprint < ::Trax::Core::Blueprint
|
19
|
+
# class Vehicle < ::Trax::Core::Blueprint
|
20
|
+
# enum :kind do
|
21
|
+
# define :car, 1, :type => "Vehicle::Car"
|
22
|
+
# define :truck, 2, :type => "Vehicle::Truck"
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# end
|
16
26
|
|
17
27
|
class Product < ::ActiveRecord::Base
|
18
28
|
include ::Trax::Model
|
@@ -60,6 +70,27 @@ module Products
|
|
60
70
|
end
|
61
71
|
end
|
62
72
|
|
73
|
+
class Vehicle < ::ActiveRecord::Base
|
74
|
+
include ::Trax::Model
|
75
|
+
include ::Trax::Model::Attributes::Mixin
|
76
|
+
|
77
|
+
mixins :unique_id => { :uuid_column => "uuid", :uuid_prefix => "9c" },
|
78
|
+
:sti_enum => true
|
79
|
+
|
80
|
+
define_attributes do
|
81
|
+
enum :kind do
|
82
|
+
define :car, 1, :type => "Vehicle::Car"
|
83
|
+
define :truck, 2, :type => "Vehicle::Truck"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Car < ::Vehicle
|
88
|
+
end
|
89
|
+
|
90
|
+
class Truck < ::Vehicle
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
63
94
|
class Widget < ::ActiveRecord::Base
|
64
95
|
include ::Trax::Model
|
65
96
|
|
@@ -78,7 +109,11 @@ class Message < ::ActiveRecord::Base
|
|
78
109
|
|
79
110
|
mixins :unique_id => { :uuid_column => "uuid", :uuid_prefix => "3a" },
|
80
111
|
:freezable => true,
|
81
|
-
:restorable => { :field => :deleted }
|
112
|
+
:restorable => { :field => :deleted },
|
113
|
+
:field_scopes => {
|
114
|
+
:by_title => true,
|
115
|
+
:by_title_case_insensitive => { :field => :title, :type => :where_lower}
|
116
|
+
}
|
82
117
|
|
83
118
|
enum :status => [ :queued, :scheduled, :delivered, :failed_delivery ]
|
84
119
|
|
@@ -114,8 +149,6 @@ end
|
|
114
149
|
class SwinglineStaplerAttributeSet < ::ActiveRecord::Base
|
115
150
|
end
|
116
151
|
|
117
|
-
|
118
|
-
|
119
152
|
class StoreCategory < ::Trax::Core::Types::Struct
|
120
153
|
include ::Trax::Model::StructExtensions
|
121
154
|
|
data/spec/support/pg/models.rb
CHANGED
@@ -6,9 +6,6 @@ module Ecommerce
|
|
6
6
|
include ::Trax::Model::Attributes::Mixin
|
7
7
|
|
8
8
|
mixins :unique_id => { :uuid_prefix => "c2" }
|
9
|
-
|
10
|
-
# belongs_to :user, :class_name => "Ecommerce::User"
|
11
|
-
# belongs_to :product, :class_name => "Ecommerce::Product"
|
12
9
|
end
|
13
10
|
|
14
11
|
class ShippingAttributes < ::Ecommerce::ProductAttributeSet
|
@@ -39,6 +36,8 @@ module Ecommerce
|
|
39
36
|
|
40
37
|
validates(:length, :numericality => {:greater_than => 0})
|
41
38
|
end
|
39
|
+
|
40
|
+
define_model_scope_for :cost, :as => :by_cost
|
42
41
|
end
|
43
42
|
end
|
44
43
|
end
|
@@ -64,6 +63,17 @@ module Ecommerce
|
|
64
63
|
define :out_of_stock, 2
|
65
64
|
define :backordered, 3
|
66
65
|
end
|
66
|
+
|
67
|
+
struct :custom_fields do
|
68
|
+
integer :cost
|
69
|
+
integer :price
|
70
|
+
integer :in_stock_quantity, :default => 0
|
71
|
+
integer :number_of_sales, :default => 0
|
72
|
+
time :last_received_at
|
73
|
+
|
74
|
+
define_model_scope_for :in_stock_quantity, :as => :by_quantity_in_stock
|
75
|
+
define_model_scope_for :last_received_at, :as => :by_last_received_at
|
76
|
+
end
|
67
77
|
end
|
68
78
|
end
|
69
79
|
|
@@ -71,6 +81,9 @@ module Ecommerce
|
|
71
81
|
class Shoes < ::Ecommerce::Product
|
72
82
|
include ::Trax::Model
|
73
83
|
include ::Trax::Model::Attributes::Mixin
|
84
|
+
|
85
|
+
define_attributes do
|
86
|
+
end
|
74
87
|
end
|
75
88
|
|
76
89
|
class MensShoes < ::Ecommerce::Products::Shoes
|
@@ -81,14 +94,12 @@ module Ecommerce
|
|
81
94
|
string :name, :default => "Some Shoe Name"
|
82
95
|
boolean :active, :default => true
|
83
96
|
|
84
|
-
struct :custom_fields do
|
97
|
+
struct :custom_fields, :extend => ::Ecommerce::Product::Fields::CustomFields do
|
85
98
|
string :primary_utility, :default => "Skateboarding"
|
86
99
|
string :sole_material
|
87
100
|
boolean :has_shoelaces
|
88
|
-
|
89
|
-
|
90
|
-
integer :cost
|
91
|
-
integer :price
|
101
|
+
|
102
|
+
array :tags
|
92
103
|
|
93
104
|
enum :color, :default => :blue do
|
94
105
|
define :red, 1
|
@@ -112,6 +123,7 @@ module Ecommerce
|
|
112
123
|
end
|
113
124
|
|
114
125
|
define_model_scopes_for(:primary_utility, :has_shoelaces, :size)
|
126
|
+
define_model_scope_for :tags, :as => :by_tags
|
115
127
|
end
|
116
128
|
end
|
117
129
|
end
|
@@ -4,7 +4,7 @@ describe ::Trax::Model::Attributes::Types::Struct, :postgres => true do
|
|
4
4
|
subject{ ::Ecommerce::Products::MensShoes::Fields::CustomFields }
|
5
5
|
|
6
6
|
it { expect(subject.new.primary_utility).to eq "Skateboarding" }
|
7
|
-
it { expect(subject.new.sole_material).to eq
|
7
|
+
it { expect(subject.new.sole_material).to eq "" }
|
8
8
|
|
9
9
|
context "attribute definition" do
|
10
10
|
subject { ::Ecommerce::ShippingAttributes.new }
|
@@ -69,6 +69,86 @@ describe ::Trax::Model::Attributes::Types::Struct, :postgres => true do
|
|
69
69
|
it { expect(subject.by_custom_fields_size(:mens_6, :mens_7)).to include(mens_6, mens_7) }
|
70
70
|
it { expect(subject.by_custom_fields_size(:mens_6, :mens_7)).to_not include(mens_10) }
|
71
71
|
end
|
72
|
+
|
73
|
+
context "array property" do
|
74
|
+
before(:all) do
|
75
|
+
@item_1 = ::Ecommerce::Products::MensShoes.create(:custom_fields => { :tags => ['skateboarding'] })
|
76
|
+
@item_2 = ::Ecommerce::Products::MensShoes.create(:custom_fields => { :tags => ['skateboarding', 'walking']})
|
77
|
+
@item_3 = ::Ecommerce::Products::MensShoes.create(:custom_fields => { :tags => ['running']})
|
78
|
+
end
|
79
|
+
|
80
|
+
subject { ::Ecommerce::Products::MensShoes.all }
|
81
|
+
|
82
|
+
it { expect(subject.by_tags('skateboarding')).to include(@item_1, @item_2) }
|
83
|
+
it { expect(subject.by_tags('skateboarding')).to_not include(@item_3) }
|
84
|
+
it { expect(subject.by_tags('running')).to include(@item_3) }
|
85
|
+
it { expect(subject.by_tags('running')).to_not include(@item_1, @item_2) }
|
86
|
+
end
|
87
|
+
|
88
|
+
context "time property" do
|
89
|
+
before(:all) do
|
90
|
+
@timestamp_1 = "2013-01-01 07:00:00"
|
91
|
+
@timestamp_2 = "2014-01-01 07:00:00"
|
92
|
+
@timestamp_3 = "2015-01-01 07:00:00"
|
93
|
+
@item_1 = ::Ecommerce::Products::MensShoes.create(:custom_fields => { :last_received_at => @timestamp_1 })
|
94
|
+
@item_2 = ::Ecommerce::Products::MensShoes.create(:custom_fields => { :last_received_at => @timestamp_2 })
|
95
|
+
@item_3 = ::Ecommerce::Products::MensShoes.create(:custom_fields => { :last_received_at => @timestamp_3 })
|
96
|
+
end
|
97
|
+
|
98
|
+
subject { ::Ecommerce::Products::MensShoes.all }
|
99
|
+
|
100
|
+
context "greater than" do
|
101
|
+
it { expect(subject.by_last_received_at_gt(@timestamp_2)).to include(@item_3) }
|
102
|
+
it { expect(subject.by_last_received_at_gt(@timestamp_2)).to_not include(@item_2, @item1) }
|
103
|
+
end
|
104
|
+
|
105
|
+
context "less than" do
|
106
|
+
it { expect(subject.by_last_received_at_lt(@timestamp_2)).to include(@item_1) }
|
107
|
+
it { expect(subject.by_last_received_at_lt(@timestamp_2)).to_not include(@item_3, @item_2) }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "numeric property" do
|
112
|
+
before(:all) do
|
113
|
+
@item_1 = ::Ecommerce::Products::MensShoes.create(:custom_fields => { :in_stock_quantity => 1 })
|
114
|
+
@item_2 = ::Ecommerce::Products::MensShoes.create(:custom_fields => { :in_stock_quantity => 2 })
|
115
|
+
@item_3 = ::Ecommerce::Products::MensShoes.create(:custom_fields => { :in_stock_quantity => 3 })
|
116
|
+
end
|
117
|
+
|
118
|
+
subject { ::Ecommerce::Products::MensShoes.all }
|
119
|
+
|
120
|
+
|
121
|
+
context "less than" do
|
122
|
+
it { expect(subject.by_quantity_in_stock_lt(2)).to include(@item_1) }
|
123
|
+
it { expect(subject.by_quantity_in_stock_lt(2)).to_not include(@item_2, @item_3) }
|
124
|
+
|
125
|
+
context "or equal" do
|
126
|
+
it { expect(subject.by_quantity_in_stock_lte(2)).to include(@item_1, @item_2) }
|
127
|
+
it { expect(subject.by_quantity_in_stock_lt(2)).to_not include(@item_3) }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context "greater than" do
|
132
|
+
it { expect(subject.by_quantity_in_stock_gt(2)).to include(@item_3) }
|
133
|
+
it { expect(subject.by_quantity_in_stock_gt(2)).to_not include(@item_2, @item_1) }
|
134
|
+
|
135
|
+
context "or equal" do
|
136
|
+
it { expect(subject.by_quantity_in_stock_gte(2)).to include(@item_3, @item_2) }
|
137
|
+
it { expect(subject.by_quantity_in_stock_gte(2)).to_not include(@item_1) }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "equal" do
|
142
|
+
it {
|
143
|
+
values = subject.by_quantity_in_stock_eq(2).map(&:custom_fields).map(&:in_stock_quantity)
|
144
|
+
expect(values).to include(2)
|
145
|
+
}
|
146
|
+
it {
|
147
|
+
values = subject.by_quantity_in_stock_eq(2).map(&:custom_fields).map(&:in_stock_quantity)
|
148
|
+
expect(values).to_not include(1, 3)
|
149
|
+
}
|
150
|
+
end
|
151
|
+
end
|
72
152
|
end
|
73
153
|
|
74
154
|
context "dirty attributes" do
|
@@ -1,7 +1,47 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe ::Trax::Model::Mixins::FieldScopes do
|
4
|
-
|
4
|
+
let(:known_title) { "Whatever's Clever: The Questioning" }
|
5
|
+
let(:other_known_title) { "Whatever's Clever: The Reckoning" }
|
6
|
+
let(:unknown_title) { "Most Known Unkown" }
|
7
|
+
let(:known_titles) { [known_title, other_known_title] }
|
8
|
+
let!(:known_message) { ::Message.create(:title => known_title) }
|
9
|
+
let!(:other_known_message) { ::Message.create(:title => other_known_title) }
|
10
|
+
let!(:known_titles_relation) { ::Message.where(id: known_message.id).select(:title) }
|
11
|
+
let!(:known_titles_downcased_relation) { ::Message.where(id: known_message.id).select('lower(title)') }
|
12
|
+
let!(:known_titles_upcased_relation) { ::Message.where(id: known_message.id).select('upper(title)') }
|
5
13
|
|
6
|
-
|
14
|
+
subject { ::Message }
|
15
|
+
|
16
|
+
context "type 'where'" do
|
17
|
+
it { expect(subject.by_title(known_title)).to be_present }
|
18
|
+
it { expect(subject.by_title(known_title.downcase)).to be_empty }
|
19
|
+
it { expect(subject.by_title(known_title.upcase)).to be_empty }
|
20
|
+
|
21
|
+
it { expect(subject.by_title(*known_titles)).to be_present }
|
22
|
+
it { expect(subject.by_title(*known_titles.map(&:downcase))).to be_empty }
|
23
|
+
it { expect(subject.by_title(*known_titles.map(&:upcase))).to be_empty }
|
24
|
+
|
25
|
+
it { expect(subject.by_title(known_titles_relation)).to be_present }
|
26
|
+
it { expect(subject.by_title(known_titles_downcased_relation)).to be_empty }
|
27
|
+
it { expect(subject.by_title(known_titles_upcased_relation)).to be_empty }
|
28
|
+
|
29
|
+
it { expect(subject.by_title(unknown_title)).to be_empty }
|
30
|
+
end
|
31
|
+
|
32
|
+
context "type 'where_lower'" do
|
33
|
+
it { expect(subject.by_title_case_insensitive(known_title)).to be_present }
|
34
|
+
it { expect(subject.by_title_case_insensitive(known_title.downcase)).to be_present }
|
35
|
+
it { expect(subject.by_title_case_insensitive(known_title.upcase)).to be_present }
|
36
|
+
|
37
|
+
it { expect(subject.by_title_case_insensitive(*known_titles)).to be_present }
|
38
|
+
it { expect(subject.by_title_case_insensitive(*known_titles.map(&:downcase))).to be_present }
|
39
|
+
it { expect(subject.by_title_case_insensitive(*known_titles.map(&:upcase))).to be_present }
|
40
|
+
|
41
|
+
it { expect(subject.by_title_case_insensitive(known_titles_relation)).to be_empty }
|
42
|
+
it { expect(subject.by_title_case_insensitive(known_titles_downcased_relation)).to be_present }
|
43
|
+
it { expect(subject.by_title_case_insensitive(known_titles_upcased_relation)).to be_empty }
|
44
|
+
|
45
|
+
it { expect(subject.by_title_case_insensitive(unknown_title)).to be_empty }
|
46
|
+
end
|
7
47
|
end
|
data/spec/trax/string_spec.rb
CHANGED
@@ -3,10 +3,12 @@ describe ::String do
|
|
3
3
|
let(:product) { ::Product.create(:name => "iMac") }
|
4
4
|
subject{ "#{product.uuid}" }
|
5
5
|
|
6
|
+
it{ expect(subject.uuid).to be_instance_of(::Trax::Model::UUID) }
|
7
|
+
|
6
8
|
its(:uuid) { should be_instance_of(::Trax::Model::UUID) }
|
7
9
|
|
8
10
|
context "when not a uuid length" do
|
9
11
|
let(:truncated_uuid) { subject[0..8] }
|
10
|
-
it { truncated_uuid.uuid.
|
12
|
+
it { expect(truncated_uuid.uuid).to be_nil }
|
11
13
|
end
|
12
14
|
end
|
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", "~> 0.0.
|
21
|
+
spec.add_dependency "trax_core", "~> 0.0.83"
|
22
22
|
spec.add_dependency "default_value_for", "~> 3.0.0"
|
23
23
|
spec.add_dependency "simple_enum"
|
24
24
|
spec.add_development_dependency "hashie", ">= 3.4.2"
|
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.
|
4
|
+
version: 0.0.98
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Ayre
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trax_core
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.0.
|
19
|
+
version: 0.0.83
|
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: 0.0.
|
26
|
+
version: 0.0.83
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: default_value_for
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -347,6 +347,7 @@ files:
|
|
347
347
|
- lib/trax/model/mixins/field_scopes.rb
|
348
348
|
- lib/trax/model/mixins/id_scopes.rb
|
349
349
|
- lib/trax/model/mixins/sort_by_scopes.rb
|
350
|
+
- lib/trax/model/mixins/sti_enum.rb
|
350
351
|
- lib/trax/model/mti.rb
|
351
352
|
- lib/trax/model/mti/abstract.rb
|
352
353
|
- lib/trax/model/mti/entity.rb
|
@@ -357,12 +358,12 @@ files:
|
|
357
358
|
- lib/trax/model/scopes.rb
|
358
359
|
- lib/trax/model/sti.rb
|
359
360
|
- lib/trax/model/sti/attributes.rb
|
361
|
+
- lib/trax/model/string_extensions.rb
|
360
362
|
- lib/trax/model/struct_extensions.rb
|
361
363
|
- lib/trax/model/unique_id.rb
|
362
364
|
- lib/trax/model/uuid.rb
|
363
365
|
- lib/trax/model/uuid_array.rb
|
364
366
|
- lib/trax/model/uuid_prefix.rb
|
365
|
-
- lib/trax/string.rb
|
366
367
|
- lib/trax/validators/boolean_validator.rb
|
367
368
|
- lib/trax/validators/email_validator.rb
|
368
369
|
- lib/trax/validators/enum_attribute_validator.rb
|
@@ -389,6 +390,7 @@ files:
|
|
389
390
|
- spec/trax/model/freezable_spec.rb
|
390
391
|
- spec/trax/model/matchable_spec.rb
|
391
392
|
- spec/trax/model/mixins/field_scopes_spec.rb
|
393
|
+
- spec/trax/model/mixins/sti_enum_spec.rb
|
392
394
|
- spec/trax/model/registry_spec.rb
|
393
395
|
- spec/trax/model/restorable_spec.rb
|
394
396
|
- spec/trax/model/sti/attributes_spec.rb
|
@@ -443,6 +445,7 @@ test_files:
|
|
443
445
|
- spec/trax/model/freezable_spec.rb
|
444
446
|
- spec/trax/model/matchable_spec.rb
|
445
447
|
- spec/trax/model/mixins/field_scopes_spec.rb
|
448
|
+
- spec/trax/model/mixins/sti_enum_spec.rb
|
446
449
|
- spec/trax/model/registry_spec.rb
|
447
450
|
- spec/trax/model/restorable_spec.rb
|
448
451
|
- spec/trax/model/sti/attributes_spec.rb
|