torque-postgresql 0.2.13 → 0.2.14

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: 6a19c0e5178591e3b9ea84d11df0b21d34f135df
4
- data.tar.gz: 06c0b78395434ae948854222ce3ad015ce55cd61
3
+ metadata.gz: d0ed00788be18944446039d37828775e220ab9c8
4
+ data.tar.gz: 1c7d9ab51f7715931944c9442a84d1798a73068a
5
5
  SHA512:
6
- metadata.gz: 90146d035616baa734f5a068028b32bf5a9605539d1f58a76f8ce107b65eeeaed8e88776d6bde29460ab340667b96a14fbf0e18f196c98d805f479511a5db167
7
- data.tar.gz: f5f126cf74c95a4c3ccb4d6aab8f69c82a5e37215ea79928d2bc261f819d00db8c6721e059e954c9dd353182a74794c31668630ce58dac49f6274c5a113d919f
6
+ metadata.gz: 993b2f4cb92a254f801db65f8ec97730e260a427e62d4cde9ea986cc6a63b813e28c4179da1ec84e6516889a0bf4e7591774468e67afaea267495a257e779533
7
+ data.tar.gz: a9f78472309cd8604d692f9b2128027b41e16a6d1768f4b02dbf3fd00bf80a471c0cb550566e3960da4b2be43a8cfb8ac4be725c47dddd2d25c66f9b95f2ba88
@@ -10,6 +10,10 @@ module Torque
10
10
  new(row['typname'])
11
11
  end
12
12
 
13
+ def self.auto_initialize?
14
+ Torque::PostgreSQL.config.enum.initializer
15
+ end
16
+
13
17
  def initialize(name)
14
18
  @name = name
15
19
  @klass = Attributes::Enum.lookup(name)
@@ -49,8 +49,7 @@ module Torque
49
49
  # The value must be Integer when no precision is given
50
50
  def deserialize(value)
51
51
  return if value.blank?
52
- parts = ActiveSupport::Duration::ISO8601Parser.new(value).parse!
53
- parts_to_duration(parts)
52
+ ActiveSupport::Duration.parse(value)
54
53
  end
55
54
 
56
55
  # Uses the ActiveSupport::Duration::ISO8601Serializer
@@ -58,6 +57,7 @@ module Torque
58
57
  def serialize(value)
59
58
  return if value.blank?
60
59
  value = cast(value) unless value.is_a?(ActiveSupport::Duration)
60
+ value = remove_weeks(value) if value.parts.to_h.key?(:weeks)
61
61
  value.iso8601(precision: @scale)
62
62
  end
63
63
 
@@ -73,20 +73,31 @@ module Torque
73
73
 
74
74
  # Transform a list of parts into a duration object
75
75
  def parts_to_duration(parts)
76
- parts = parts.to_h.with_indifferent_access.slice(*CAST_PARTS)
76
+ parts = parts.to_h.slice(*CAST_PARTS)
77
77
  return 0.seconds if parts.blank?
78
78
 
79
79
  seconds = 0
80
80
  parts = parts.map do |part, num|
81
81
  num = num.to_i unless num.is_a?(Numeric)
82
- if num > 0
83
- seconds += num.send(part).value
84
- [part.to_sym, num]
85
- end
82
+ next if num <= 0
83
+
84
+ seconds += num.send(part).value
85
+ [part.to_sym, num]
86
86
  end
87
+
87
88
  ActiveSupport::Duration.new(seconds, parts.compact)
88
89
  end
89
90
 
91
+ # As PostgreSQL converts weeks in duration to days, intercept duration
92
+ # values with weeks and turn them into days before serializing so it
93
+ # won't break because the following issues
94
+ # https://github.com/crashtech/torque-postgresql/issues/26
95
+ # https://github.com/rails/rails/issues/34655
96
+ def remove_weeks(value)
97
+ parts = value.parts.dup
98
+ parts[:days] += parts.delete(:weeks) * 7
99
+ ActiveSupport::Duration.new(value.seconds.to_i, parts)
100
+ end
90
101
  end
91
102
  end
92
103
  end
@@ -29,7 +29,7 @@ module Torque
29
29
  # Use local type map to identify attribute decorator
30
30
  def define_attribute_method(attribute)
31
31
  type = attribute_types[attribute]
32
- super unless TypeMap.lookup(type, self, attribute, true)
32
+ super unless TypeMap.lookup(type, self, attribute)
33
33
  end
34
34
 
35
35
  end
@@ -4,15 +4,14 @@ module Torque
4
4
  module Builder
5
5
  class Enum
6
6
 
7
- attr_accessor :klass, :attribute, :subtype, :initial, :options, :values
7
+ attr_accessor :klass, :attribute, :subtype, :options, :values
8
8
 
9
9
  # Start a new builder of methods for composite values on
10
10
  # ActiveRecord::Base
11
- def initialize(klass, attribute, subtype, initial, options)
11
+ def initialize(klass, attribute, subtype, options)
12
12
  @klass = klass
13
13
  @attribute = attribute.to_s
14
14
  @subtype = subtype
15
- @initial = initial
16
15
  @options = options
17
16
 
18
17
  @values = subtype.klass.values
@@ -53,9 +52,12 @@ module Torque
53
52
  # with the base class methods
54
53
  def conflicting?
55
54
  return false if options[:force] == true
55
+ attributes = attribute.pluralize
56
56
 
57
- dangerous?(attribute.pluralize, true)
58
- dangerous?(attribute + '_text')
57
+ dangerous?(attributes, true)
58
+ dangerous?("#{attributes}_options", true)
59
+ dangerous?("#{attributes}_texts", true)
60
+ dangerous?("#{attribute}_text")
59
61
 
60
62
  values_methods.each do |attr, list|
61
63
  list.map(&method(:dangerous?))
@@ -63,19 +65,11 @@ module Torque
63
65
 
64
66
  return false
65
67
  rescue Interrupt => err
66
- if !initial
67
- raise ArgumentError, <<-MSG.strip.gsub(/\n +/, ' ')
68
- #{subtype.class.name} was not able to generate requested
69
- methods because the method #{err} already exists in
70
- #{klass.name}.
71
- MSG
72
- else
73
- warn <<-MSG.strip.gsub(/\n +/, ' ')
74
- #{subtype.class.name} was not able to autoload on
75
- #{klass.name} because the method #{err} already exists.
76
- MSG
77
- return true
78
- end
68
+ raise ArgumentError, <<-MSG.strip.gsub(/\n +/, ' ')
69
+ #{subtype.class.name} was not able to generate requested
70
+ methods because the method #{err} already exists in
71
+ #{klass.name}.
72
+ MSG
79
73
  end
80
74
 
81
75
  # Create all methods needed
@@ -102,45 +96,60 @@ module Torque
102
96
 
103
97
  # Create the method that allow access to the list of values
104
98
  def plural
105
- klass.singleton_class.module_eval <<-STR, __FILE__, __LINE__ + 1
106
- def #{attribute.pluralize} # def statuses
107
- ::#{subtype.klass.name}.values # ::Enum::Status.values
108
- end # end
109
- STR
99
+ attr = attribute
100
+ enum_klass = subtype.klass
101
+ klass.singleton_class.module_eval do
102
+ # def self.statuses() statuses end
103
+ define_method(attr.pluralize) do
104
+ enum_klass.values
105
+ end
106
+
107
+ # def self.statuses_texts() members.map(&:text) end
108
+ define_method(attr.pluralize + '_texts') do
109
+ enum_klass.members.map do |member|
110
+ member.text(attr, self)
111
+ end
112
+ end
113
+
114
+ # def self.statuses_options() statuses_texts.zip(statuses) end
115
+ define_method(attr.pluralize + '_options') do
116
+ enum_klass.values
117
+ end
118
+ end
110
119
  end
111
120
 
112
121
  # Create the method that turn the attribute value into text using
113
122
  # the model scope
114
123
  def text
115
- klass.module_eval <<-STR, __FILE__, __LINE__ + 1
116
- def #{attribute}_text # def status_text
117
- #{attribute}.text('#{attribute}', self) # status.text('status', self)
118
- end # end
119
- STR
124
+ attr = attribute
125
+ klass.module_eval do
126
+ # def status_text() status.text('status', self) end
127
+ define_method("#{attr}_text") { send(attr).text(attr, self) }
128
+ end
120
129
  end
121
130
 
122
131
  # Create all the methods that represent actions related to the
123
132
  # attribute value
124
133
  def all_values
125
- values_methods.each do |val, list|
126
- klass.module_eval <<-STR, __FILE__, __LINE__ + 1
127
- scope :#{list[0]}, -> do # scope :disabled, -> do
128
- where(#{attribute}: '#{val}') # where(status: 'disabled')
129
- end # end
130
- STR
131
- klass.module_eval <<-STR, __FILE__, __LINE__ + 1
132
- def #{list[1]} # def disabled?
133
- #{attribute}.#{val}? # status.disabled?
134
- end # end
135
-
136
- def #{list[2]} # def disabled!
137
- if enum_save_on_bang # if enum_save_on_bang
138
- update!(#{attribute}: '#{val}') # update!(status: 'disabled')
139
- else # else
140
- #{attribute}.#{val}! # status.disabled!
141
- end # end
142
- end # end
143
- STR
134
+ attr = attribute
135
+ vals = values_methods
136
+ klass.module_eval do
137
+ vals.each do |val, list|
138
+ # scope :disabled, -> { where(status: 'disabled') }
139
+ scope list[0], -> { where(attr => val) }
140
+
141
+ # def disabled? status.disabled? end
142
+ define_method(list[1]) { send(attr).public_send("#{val}?") }
143
+
144
+ # def disabled! enum_save_on_bang ? update!(status: 'disabled') : status.disabled! end
145
+ define_method(list[2]) do
146
+ if enum_save_on_bang
147
+ update!(attr => val)
148
+ else
149
+ send(attr).public_send("#{val}!")
150
+ end
151
+ end
152
+ end
144
153
  end
145
154
  end
146
155
 
@@ -22,6 +22,17 @@ module Torque
22
22
  namespace.const_set(const, Class.new(Enum))
23
23
  end
24
24
 
25
+ # Provide a method on the given class to setup which enums will be
26
+ # manually initialized
27
+ def include_on(klass)
28
+ method_name = Torque::PostgreSQL.config.enum.base_method
29
+ klass.singleton_class.class_eval do
30
+ define_method(method_name) do |*args, **options|
31
+ Torque::PostgreSQL::Attributes::TypeMap.decorate(self, args, **options)
32
+ end
33
+ end
34
+ end
35
+
25
36
  # You can specify the connection name for each enum
26
37
  def connection_specification_name
27
38
  return self == Enum ? 'primary' : superclass.connection_specification_name
@@ -47,6 +58,16 @@ module Torque
47
58
  values.dup.map(&method(:new))
48
59
  end
49
60
 
61
+ # Get the list of the values translated by I18n
62
+ def texts
63
+ members.map(&:text)
64
+ end
65
+
66
+ # Get a list of values translated and ready for select
67
+ def to_options
68
+ texts.zip(values)
69
+ end
70
+
50
71
  # Fetch a value from the list
51
72
  # see https://github.com/rails/rails/blob/v5.0.0/activerecord/lib/active_record/fixtures.rb#L656
52
73
  # see https://github.com/rails/rails/blob/v5.0.0/activerecord/lib/active_record/validations/uniqueness.rb#L101
@@ -88,21 +109,6 @@ module Torque
88
109
 
89
110
  end
90
111
 
91
- # Extension of the ActiveRecord::Base to initiate the enum features
92
- module Base
93
-
94
- method_name = Torque::PostgreSQL.config.enum.base_method
95
- module_eval <<-STR, __FILE__, __LINE__ + 1
96
- def #{method_name}(*args, **options)
97
- args.each do |attribute|
98
- type = attribute_types[attribute.to_s]
99
- TypeMap.lookup(type, self, attribute.to_s, false, options)
100
- end
101
- end
102
- STR
103
-
104
- end
105
-
106
112
  # Override string initializer to check for a valid value
107
113
  def initialize(value)
108
114
  str_value = value.is_a?(Numeric) ? self.class.values[value.to_i] : value.to_s
@@ -171,7 +177,7 @@ module Torque
171
177
 
172
178
  if attr && model
173
179
  values[:attr] = attr
174
- values[:model] = model.class.model_name.i18n_key
180
+ values[:model] = model.model_name.i18n_key
175
181
  list_from = :i18n_scopes
176
182
  end
177
183
 
@@ -217,32 +223,16 @@ module Torque
217
223
 
218
224
  end
219
225
 
220
- # Extend ActiveRecord::Base so it can have the initializer
221
- ActiveRecord::Base.extend Enum::Base
222
-
223
226
  # Create the methods related to the attribute to handle the enum type
224
- TypeMap.register_type Adapter::OID::Enum do |subtype, attribute, initial = false, options = nil|
225
- break if initial && !Torque::PostgreSQL.config.enum.initializer
226
- options = {} if options.nil?
227
-
227
+ TypeMap.register_type Adapter::OID::Enum do |subtype, attribute, options = nil|
228
228
  # Generate methods on self class
229
- builder = Builder::Enum.new(self, attribute, subtype, initial, options)
229
+ builder = Builder::Enum.new(self, attribute, subtype, options || {})
230
230
  break if builder.conflicting?
231
231
  builder.build
232
232
 
233
233
  # Mark the enum as defined
234
234
  defined_enums[attribute] = subtype.klass
235
235
  end
236
-
237
- # Define a method to find yet to define constants
238
- Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:const_missing) do |name|
239
- Enum.lookup(name)
240
- end
241
-
242
- # Define a helper method to get a sample value
243
- Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:sample) do |name|
244
- Enum.lookup(name).sample
245
- end
246
236
  end
247
237
  end
248
238
  end
@@ -10,6 +10,34 @@ module Torque
10
10
  @types ||= {}
11
11
  end
12
12
 
13
+ # Store which elements should be initialized
14
+ def decorable
15
+ @decorable ||= Hash.new{ |h, k| h[k] = [] }
16
+ end
17
+
18
+ # List of options for each individual attribute on each klass
19
+ def options
20
+ @options ||= Hash.new{ |h, k| h[k] = {} }
21
+ end
22
+
23
+ # Mark the list of attributes on the given class that can be decorated
24
+ def decorate(klass, *attributes, **set_options)
25
+ attributes.flatten.each do |attribute|
26
+ decorable[klass] << attribute.to_s
27
+ options[klass][attribute.to_s] = set_options.deep_dup
28
+ end
29
+ end
30
+
31
+ # Force the list of attributes on the given class to be decorated by
32
+ # this type mapper
33
+ def decorate!(klass, *attributes, **options)
34
+ decorate(klass, *attributes, **options)
35
+ attributes.flatten.map do |attribute|
36
+ type = klass.attribute_types[attribute.to_s]
37
+ lookup(type, klass, attribute.to_s)
38
+ end
39
+ end
40
+
13
41
  # Register a type that can be processed by a given block
14
42
  def register_type(key, &block)
15
43
  raise_type_defined(key) if present?(key)
@@ -17,9 +45,12 @@ module Torque
17
45
  end
18
46
 
19
47
  # Search for a type match and process it if any
20
- def lookup(key, klass, *args)
21
- return unless present?(key)
22
- klass.instance_exec(key, *args, &types[key.class])
48
+ def lookup(key, klass, attribute, *args)
49
+ return unless present?(key) && decorable?(key, klass, attribute)
50
+
51
+ set_options = options[klass][attribute]
52
+ args.unshift(set_options) unless set_options.nil?
53
+ klass.instance_exec(key, attribute, *args, &types[key.class])
23
54
  rescue LocalJumpError
24
55
  # There's a bug or misbehavior that blocks being called through
25
56
  # instance_exec don't accept neither return nor break
@@ -31,6 +62,13 @@ module Torque
31
62
  types.key?(key.class)
32
63
  end
33
64
 
65
+ # Check whether the given attribute on the given klass is
66
+ # decorable by this type mapper
67
+ def decorable?(key, klass, attribute)
68
+ key.class.auto_initialize? ||
69
+ (decorable.key?(klass) && decorable[klass].include?(attribute.to_s))
70
+ end
71
+
34
72
  # Message when trying to define multiple types
35
73
  def raise_type_defined(key)
36
74
  raise ArgumentError, <<-MSG.strip
@@ -87,6 +87,20 @@ module Torque
87
87
  "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}"
88
88
  end
89
89
 
90
+ # For all main purposes, physical inherited classes should have
91
+ # base_class as their own
92
+ def base_class
93
+ return super unless physically_inherited?
94
+ self
95
+ end
96
+
97
+ # Primary key is one exception when getting information about the class,
98
+ # it must returns the superclass PK
99
+ def primary_key
100
+ return super unless physically_inherited?
101
+ superclass.primary_key
102
+ end
103
+
90
104
  # Add an additional check to return the name of the table even when the
91
105
  # class is inherited, but only if it is a physical inheritance
92
106
  def compute_table_name
@@ -6,6 +6,20 @@ module Torque
6
6
  # Get information from the running rails app
7
7
  initializer 'torque-postgresql' do |app|
8
8
  Torque::PostgreSQL.config.eager_load = app.config.eager_load
9
+
10
+ # Include enum on ActiveRecord::Base so it can have the correct enum
11
+ # initializer
12
+ Torque::PostgreSQL::Attributes::Enum.include_on(ActiveRecord::Base)
13
+
14
+ # Define a method to find yet to define constants
15
+ Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:const_missing) do |name|
16
+ Torque::PostgreSQL::Attributes::Enum.lookup(name)
17
+ end
18
+
19
+ # Define a helper method to get a sample value
20
+ Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:sample) do |name|
21
+ Torque::PostgreSQL::Attributes::Enum.lookup(name).sample
22
+ end
9
23
  end
10
24
 
11
25
  end
@@ -112,11 +112,24 @@ module Torque
112
112
  .inheritance.auto_cast_column_name.to_sym
113
113
  end
114
114
  end
115
+
116
+ # When a relation is created, force the attributes to be defined,
117
+ # because the type mapper may add new methods to the model. This happens
118
+ # for the given model Klass and its inheritances
119
+ module Initializer
120
+ def initialize(klass, *)
121
+ super
122
+
123
+ klass.superclass.send(:relation) if klass.define_attribute_methods &&
124
+ klass.superclass != ActiveRecord::Base && !klass.superclass.abstract_class?
125
+ end
126
+ end
115
127
  end
116
128
 
117
129
  # Include the methos here provided and then change the constants to ensure
118
130
  # the operation of ActiveRecord Relation
119
131
  ActiveRecord::Relation.include Relation
132
+ ActiveRecord::Relation.prepend Relation::Initializer
120
133
 
121
134
  warn_level = $VERBOSE
122
135
  $VERBOSE = nil
@@ -11,7 +11,6 @@ module Torque
11
11
  @data_sources_model_names = {}
12
12
  @inheritance_dependencies = {}
13
13
  @inheritance_associations = {}
14
- @cached_data_sources_size = 0
15
14
  end
16
15
 
17
16
  def initialize_dup(*) # :nodoc:
@@ -19,7 +18,30 @@ module Torque
19
18
  @data_sources_model_names = @data_sources_model_names.dup
20
19
  @inheritance_dependencies = @inheritance_dependencies.dup
21
20
  @inheritance_associations = @inheritance_associations.dup
22
- @cached_data_sources_size = @cached_data_sources_size.dup
21
+ end
22
+
23
+ def encode_with(coder) # :nodoc:
24
+ super
25
+ coder["data_sources_model_names"] = @data_sources_model_names
26
+ coder["inheritance_dependencies"] = @inheritance_dependencies
27
+ coder["inheritance_associations"] = @inheritance_associations
28
+ end
29
+
30
+ def init_with(coder) # :nodoc:
31
+ super
32
+ @data_sources_model_names = coder["data_sources_model_names"]
33
+ @inheritance_dependencies = coder["inheritance_dependencies"]
34
+ @inheritance_associations = coder["inheritance_associations"]
35
+ end
36
+
37
+ def add(table_name, *) # :nodoc:
38
+ super
39
+
40
+ # Reset inheritance information when a table is added
41
+ if @data_sources.key?(table_name)
42
+ @inheritance_dependencies.clear
43
+ @inheritance_associations.clear
44
+ end
23
45
  end
24
46
 
25
47
  def clear! # :nodoc:
@@ -27,7 +49,6 @@ module Torque
27
49
  @data_sources_model_names.clear
28
50
  @inheritance_dependencies.clear
29
51
  @inheritance_associations.clear
30
- @cached_data_sources_size = nil
31
52
  end
32
53
 
33
54
  def size # :nodoc:
@@ -43,12 +64,10 @@ module Torque
43
64
  @data_sources_model_names.delete name
44
65
  @inheritance_dependencies.delete name
45
66
  @inheritance_associations.delete name
46
- @inheritance_cache = inheritance_cache_key
47
67
  end
48
68
 
49
69
  def marshal_dump # :nodoc:
50
70
  super + [
51
- @inheritance_cache,
52
71
  @inheritance_dependencies,
53
72
  @inheritance_associations,
54
73
  @data_sources_model_names,
@@ -59,7 +78,6 @@ module Torque
59
78
  @data_sources_model_names = array.pop
60
79
  @inheritance_associations = array.pop
61
80
  @inheritance_dependencies = array.pop
62
- @inheritance_cache = array.pop
63
81
  super
64
82
  end
65
83
 
@@ -143,19 +161,10 @@ module Torque
143
161
  end
144
162
  end
145
163
 
146
- # Generates the cache key for inheitance information
147
- def inheritance_cache_key
148
- @data_sources.keys.compact.sort.join(',')
149
- end
150
-
151
164
  # Reload information about tables inheritance and dependencies, uses a
152
165
  # cache to not perform additional checkes
153
166
  def reload_inheritance_data!
154
- cache_key = inheritance_cache_key
155
-
156
- return unless @inheritance_cache != cache_key
157
- @inheritance_cache = cache_key
158
-
167
+ return if @inheritance_dependencies.present?
159
168
  @inheritance_dependencies = connection.inherited_tables
160
169
  @inheritance_associations = generate_associations
161
170
  end
@@ -1,5 +1,5 @@
1
1
  module Torque
2
2
  module PostgreSQL
3
- VERSION = '0.2.13'
3
+ VERSION = '0.2.14'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: torque-postgresql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.13
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Silva
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-22 00:00:00.000000000 Z
11
+ date: 2018-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails