activerecord-typedstore 0.6.1 → 1.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 855709ac9aceae5da46732cf436026babd98f1da
4
- data.tar.gz: 33046bf44a228c713084a6858e27dca3a9a94602
3
+ metadata.gz: 64a197e045ec0a6c19581176c6a62aaf1800ce77
4
+ data.tar.gz: ffcf6bcc94d684da15ed877cf9bda5244934145d
5
5
  SHA512:
6
- metadata.gz: 79c9e26d0c8b8ce37186c2e7dcc2ed0e9757f227dec41ee55510d3363834b458c87502c6852058e1dc6f3b0fae285fd686350691ec6f32d38ed36441ed05f822
7
- data.tar.gz: 449c5e918d80c979e4af8940c3aeea7097edb82b46c9d6ec55ae1182787e6090fc5861b770c75b85c5df0a2b915b785d274b19eb01e6139abb991d7e43f7d4ed
6
+ metadata.gz: ac1eca590e2d1e0daea3a72281219ae43d0ad47c1376bd5f155b1e66ef93e8a6cd3f0403d94d7a92de1791846b9b9df19cf1c017138eb37e767d1ad46dda2da5
7
+ data.tar.gz: 99169aeeafe4c40c39357d16e8cf6dd54de65fa97a9213635a686d5afe0da1848b00735f55fd73c77b4bd8be165c2b949b287d76334a2a82200732800be36f25
@@ -1,13 +1,10 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 2.0
5
4
  - 2.1
6
- - 2.2
5
+ - 2.2.3
6
+ - 2.3.0
7
7
  gemfile:
8
- - gemfiles/Gemfile.ar-3.2
9
- - gemfiles/Gemfile.ar-4.0
10
- - gemfiles/Gemfile.ar-4.1
11
8
  - gemfiles/Gemfile.ar-4.2
12
9
  - gemfiles/Gemfile.ar-edge
13
10
 
@@ -19,16 +16,8 @@ addons:
19
16
  postgresql: 9.3
20
17
 
21
18
  sudo: false
19
+ cache: bundler
22
20
 
23
21
  before_script:
24
22
  - mysql -e 'create database typed_store_test;'
25
23
  - psql -c 'create database typed_store_test;' -U postgres
26
-
27
- matrix:
28
- allow_failures:
29
- - gemfile: gemfiles/Gemfile.ar-edge
30
- exclude:
31
- - gemfile: gemfiles/Gemfile.ar-edge
32
- rvm: 2.1
33
- - gemfile: gemfiles/Gemfile.ar-edge
34
- rvm: 2.0
@@ -18,14 +18,14 @@ 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 'activerecord', '>= 3.2', '< 5'
21
+ spec.add_dependency 'activerecord', '>= 4.2', '< 5.1'
22
22
 
23
23
  spec.add_development_dependency 'bundler', '~> 1.3'
24
24
  spec.add_development_dependency 'rake', '~> 10'
25
25
  spec.add_development_dependency 'rspec', '~> 3'
26
26
  spec.add_development_dependency 'coveralls', '~> 0'
27
- spec.add_development_dependency 'sqlite3', '~> 0'
28
- spec.add_development_dependency 'pg', '~> 0'
29
- spec.add_development_dependency 'mysql2', '~> 0'
30
- spec.add_development_dependency 'database_cleaner', '~> 0'
27
+ spec.add_development_dependency 'sqlite3', '~> 1'
28
+ spec.add_development_dependency 'pg', '~> 0.18'
29
+ spec.add_development_dependency 'mysql2', '> 0.3'
30
+ spec.add_development_dependency 'database_cleaner', '~> 1'
31
31
  end
@@ -6,6 +6,6 @@ gem 'rake'
6
6
  gem 'rspec'
7
7
  gem 'sqlite3'
8
8
  gem 'pg', '~> 0.11'
9
- gem 'mysql2'
9
+ gem 'mysql2', '~> 0.3.10'
10
10
  gem 'database_cleaner'
11
11
  gem 'coveralls', require: false
@@ -6,6 +6,6 @@ gem 'rake'
6
6
  gem 'rspec'
7
7
  gem 'sqlite3'
8
8
  gem 'pg', '~> 0.11'
9
- gem 'mysql2'
9
+ gem 'mysql2', '~> 0.3.10'
10
10
  gem 'database_cleaner'
11
11
  gem 'coveralls', require: false
@@ -6,6 +6,6 @@ gem 'rake'
6
6
  gem 'rspec'
7
7
  gem 'sqlite3'
8
8
  gem 'pg', '~> 0.11'
9
- gem 'mysql2'
9
+ gem 'mysql2', '~> 0.3.13'
10
10
  gem 'database_cleaner'
11
11
  gem 'coveralls', require: false
@@ -6,6 +6,6 @@ gem 'rake'
6
6
  gem 'rspec'
7
7
  gem 'sqlite3'
8
8
  gem 'pg', '~> 0.11'
9
- gem 'mysql2'
9
+ gem 'mysql2', ['>= 0.3.13', '< 0.5']
10
10
  gem 'database_cleaner'
11
11
  gem 'coveralls', require: false
@@ -1,11 +1,12 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'rails', github: 'rails/rails'
3
+ gem 'activerecord', github: 'rails/rails'
4
+ gem 'arel', github: 'rails/arel'
4
5
  gem 'bundler', '~> 1.3'
5
6
  gem 'rake'
6
7
  gem 'rspec'
7
8
  gem 'sqlite3'
8
9
  gem 'pg', '~> 0.11'
9
- gem 'mysql2'
10
+ gem 'mysql2', ['>= 0.3.13', '< 0.5']
10
11
  gem 'database_cleaner'
11
12
  gem 'coveralls', require: false
@@ -1,29 +1,31 @@
1
- module ActiveRecord::TypedStore
1
+ require 'active_record/typed_store/field'
2
2
 
3
+ module ActiveRecord::TypedStore
3
4
  class DSL
5
+ attr_reader :fields, :coder
4
6
 
5
- attr_reader :columns
6
-
7
- def initialize(accessors=true)
8
- @accessors = accessors
9
- @columns = []
7
+ def initialize(options)
8
+ @coder = options.fetch(:coder) { default_coder }
9
+ @fields = {}
10
10
  yield self
11
11
  end
12
12
 
13
- def accessors
14
- @columns.select(&:accessor?).map(&:name)
13
+ def default_coder
14
+ ActiveRecord::Coders::YAMLColumn.new
15
15
  end
16
16
 
17
- [:string, :text, :integer, :float, :datetime, :date, :boolean, :any].each do |type|
18
- define_method(type) do |name, options = {}|
19
- @columns << Column.new(name, type, {accessor: @accessors}.merge(options))
20
- end
17
+ def accessors
18
+ @fields.values.select { |v| v.accessor }.map(&:name)
21
19
  end
22
20
 
23
- def decimal(name, options = {})
24
- @columns << Column.new(name, :decimal, {accessor: @accessors, limit: 20, scale: 6}.merge(options))
25
- end
21
+ delegate :keys, to: :@fields
26
22
 
23
+ NO_DEFAULT_GIVEN = Object.new
24
+ [:string, :text, :integer, :float, :datetime, :date, :boolean, :decimal, :any].each do |type|
25
+ define_method(type) do |name, **options|
26
+ @fields[name] = Field.new(name, type, options)
27
+ end
28
+ end
29
+ alias_method :date_time, :datetime
27
30
  end
28
-
29
31
  end
@@ -1,40 +1,36 @@
1
- require 'active_record/typed_store/column'
2
1
  require 'active_record/typed_store/dsl'
2
+ require 'active_record/typed_store/type'
3
3
  require 'active_record/typed_store/typed_hash'
4
4
 
5
5
  module ActiveRecord::TypedStore
6
- AR_VERSION = Gem::Version.new(ActiveRecord::VERSION::STRING)
7
- IS_AR_3_2 = AR_VERSION < Gem::Version.new('4.0')
8
- IS_AR_4_1 = AR_VERSION >= Gem::Version.new('4.1') && AR_VERSION < Gem::Version.new('4.2.0')
9
- IS_AR_4_2 = AR_VERSION >= Gem::Version.new('4.2.0')
10
-
11
6
  module Extension
12
7
  extend ActiveSupport::Concern
13
8
 
14
9
  included do
15
- class_attribute :typed_stores, instance_accessor: false
16
- class_attribute :typed_store_attributes, instance_accessor: false
10
+ class_attribute :typed_stores
17
11
  end
18
12
 
19
13
  module ClassMethods
14
+ def store_accessors
15
+ return [] unless typed_stores
16
+ typed_stores.values.map(&:accessors).flatten
17
+ end
20
18
 
21
19
  def typed_store(store_attribute, options={}, &block)
22
- dsl = DSL.new(options.fetch(:accessors, true), &block)
20
+ dsl = DSL.new(options, &block)
21
+ self.typed_stores = {}
22
+ self.typed_stores[store_attribute] = dsl
23
23
 
24
- serialize store_attribute, create_coder(store_attribute, dsl.columns).new(options[:coder])
25
- store_accessor(store_attribute, dsl.accessors)
24
+ typed_klass = TypedHash.create(dsl.fields.values)
25
+ const_set("#{store_attribute}_hash".camelize, typed_klass)
26
26
 
27
- register_typed_store_columns(store_attribute, dsl.columns)
28
- super(store_attribute, dsl) if defined?(super)
29
-
30
- dsl.accessors.each { |c| define_store_attribute_queries(store_attribute, c) }
31
-
32
- dsl
27
+ attribute(store_attribute, Type.new(typed_klass, dsl.coder))
28
+ store_accessor(store_attribute, dsl.accessors)
33
29
  end
34
30
 
35
31
  def define_attribute_methods
36
32
  super
37
- define_typed_store_attribute_methods
33
+ define_typed_store_attribute_methods if typed_stores
38
34
  end
39
35
 
40
36
  def undefine_attribute_methods # :nodoc:
@@ -42,28 +38,10 @@ module ActiveRecord::TypedStore
42
38
  @typed_store_attribute_methods_generated = false
43
39
  end
44
40
 
45
- private
46
-
47
- def create_coder(store_attribute, columns)
48
- store_class = TypedHash.create(columns)
49
- const_set("#{store_attribute}_hash".camelize, store_class)
50
- coder_class = Coder.create(store_class)
51
- const_set("#{store_attribute}_coder".camelize, coder_class)
52
- coder_class
53
- end
54
-
55
- def register_typed_store_columns(store_attribute, columns)
56
- self.typed_stores ||= {}
57
- self.typed_store_attributes ||= {}
58
- typed_stores[store_attribute] ||= {}
59
- typed_stores[store_attribute].merge!(columns.index_by(&:name))
60
- typed_store_attributes.merge!(columns.index_by { |c| c.name.to_s })
61
- end
62
-
63
41
  def define_typed_store_attribute_methods
64
42
  return if @typed_store_attribute_methods_generated
65
43
  store_accessors.each do |attribute|
66
- define_virtual_attribute_method(attribute)
44
+ define_attribute_method(attribute.to_s)
67
45
  undefine_before_type_cast_method(attribute)
68
46
  end
69
47
  @typed_store_attribute_methods_generated = true
@@ -74,120 +52,40 @@ module ActiveRecord::TypedStore
74
52
  method = "#{attribute}_before_type_cast"
75
53
  undef_method(method) if method_defined?(method)
76
54
  end
77
-
78
- def store_accessors
79
- return [] unless typed_store_attributes
80
- typed_store_attributes.values.select(&:accessor?).map(&:name).map(&:to_s)
81
- end
82
-
83
- def create_time_zone_conversion_attribute?(name, column)
84
- column ||= typed_store_attributes[name]
85
- super(name, column)
86
- end
87
-
88
- def define_store_attribute_queries(store_attribute, column_name)
89
- define_method("#{column_name}?") do
90
- query_store_attribute(store_attribute, column_name)
91
- end
92
- end
93
-
94
- end
95
-
96
- protected
97
-
98
- if IS_AR_4_2
99
- def attribute_method?(attr_name)
100
- super || store_attribute_method?(attr_name)
101
- end
102
- end
103
-
104
- def store_attribute_method?(attr_name)
105
- return unless self.class.typed_store_attributes
106
- store_attribute = self.class.typed_store_attributes[attr_name]
107
- store_attribute && store_attribute.accessor?
108
55
  end
109
56
 
110
- def write_store_attribute(store_attribute, key, value)
111
- column = store_column(store_attribute, key)
112
- if column.try(:type) == :datetime && self.class.time_zone_aware_attributes && value.respond_to?(:in_time_zone)
113
- value = value.in_time_zone
114
- end
115
-
116
- previous_value = read_store_attribute(store_attribute, key)
117
- new_value = column ? column.type_cast(value) : value
118
- attribute_will_change!(key.to_s) if new_value != previous_value
57
+ def clear_attribute_change(attr_name)
58
+ return if self.class.store_accessors.include?(attr_name.to_sym)
119
59
  super
120
60
  end
121
61
 
122
- private
123
-
124
- if IS_AR_4_2
125
- def match_attribute_method?(method_name)
126
- match = super
127
- return unless match
128
- return if match.target == 'attribute_before_type_cast'.freeze && store_attribute_method?(match.attr_name)
129
- match
130
- end
131
-
132
- def coder_for(attr_name)
133
- column = self.class.columns_hash[attr_name.to_s]
134
- return unless column && column.cast_type.is_a?(::ActiveRecord::Type::Serialized)
135
- column.cast_type.coder
136
- end
137
- else
138
- def coder_for(attr_name)
139
- self.class.serialized_attributes[attr_name]
140
- end
141
- end
142
-
143
- def write_attribute(attr_name, value)
144
- if coder = coder_for(attr_name)
145
- if coder.is_a?(ActiveRecord::TypedStore::Coder)
146
- return super(attr_name, coder.as_indifferent_hash(value))
147
- end
62
+ def write_store_attribute(store_attribute, key, value)
63
+ if typed_stores && typed_stores[store_attribute]
64
+ prev_value = read_store_attribute(store_attribute, key)
65
+ new_value = typed_stores[store_attribute].fields[key].cast(value)
66
+ attribute_will_change!(key.to_s) if new_value != prev_value
148
67
  end
149
68
 
150
69
  super
151
70
  end
152
71
 
153
- def keys_for_partial_write
154
- super & self.class.column_names
155
- end
72
+ def query_attribute(attr_name)
73
+ if self.class.store_accessors.include?(attr_name.to_sym)
74
+ value = public_send(attr_name)
156
75
 
157
- def store_column(store_attribute, key)
158
- store = store_columns(store_attribute)
159
- store && store[key]
160
- end
161
-
162
- def store_columns(store_attribute)
163
- self.class.typed_stores.try(:[], store_attribute)
164
- end
165
-
166
- # heavilly inspired from ActiveRecord::Base#query_attribute
167
- def query_store_attribute(store_attribute, key)
168
- value = read_store_attribute(store_attribute, key)
169
-
170
- case value
171
- when true then true
172
- when false, nil then false
173
- else
174
- column = store_column(store_attribute, key)
175
-
176
- if column.number?
177
- !value.zero?
76
+ case value
77
+ when true then true
78
+ when false, nil then false
178
79
  else
179
- !value.blank?
80
+ if value.respond_to?(:zero?)
81
+ !value.zero?
82
+ else
83
+ !value.blank?
84
+ end
180
85
  end
86
+ else
87
+ super
181
88
  end
182
89
  end
183
-
184
- end
185
-
186
- require 'active_record/typed_store/ar_32_fallbacks' if IS_AR_3_2
187
- require 'active_record/typed_store/coder'
188
-
189
- unless IS_AR_3_2
190
- ActiveModel::AttributeMethods::ClassMethods.send(:alias_method, :define_virtual_attribute_method, :define_attribute_method)
191
90
  end
192
-
193
91
  end
@@ -0,0 +1,76 @@
1
+ module ActiveRecord::TypedStore
2
+ class Field
3
+ attr_reader :array, :blank, :name, :default, :type, :null, :accessor, :type_sym
4
+
5
+ def initialize(name, type, options={})
6
+ type_options = options.slice(:scale, :limit, :precision)
7
+ @type = lookup_type(type, type_options)
8
+ @type_sym = type
9
+
10
+ @accessor = options.fetch(:accessor, true)
11
+ @name = name
12
+ if options.key?(:default)
13
+ @default = extract_default(options[:default])
14
+ end
15
+ @null = options.fetch(:null, true)
16
+ @blank = options.fetch(:blank, true)
17
+ @array = options.fetch(:array, false)
18
+ end
19
+
20
+ def has_default?
21
+ defined?(@default)
22
+ end
23
+
24
+ def cast(value)
25
+ casted_value = type_cast(value)
26
+ if !blank
27
+ casted_value = default if casted_value.blank?
28
+ elsif !null
29
+ casted_value = default if casted_value.nil?
30
+ end
31
+ casted_value
32
+ end
33
+
34
+ private
35
+
36
+ TYPES = {
37
+ boolean: ::ActiveRecord::Type::Boolean,
38
+ integer: ::ActiveRecord::Type::Integer,
39
+ string: ::ActiveRecord::Type::String,
40
+ float: ::ActiveRecord::Type::Float,
41
+ date: ::ActiveRecord::Type::Date,
42
+ datetime: ::ActiveRecord::Type::DateTime,
43
+ decimal: ::ActiveRecord::Type::Decimal,
44
+ any: ::ActiveRecord::Type::Value,
45
+ }
46
+
47
+ def lookup_type(type, options)
48
+ TYPES.fetch(type).new(**options)
49
+ end
50
+
51
+ def extract_default(value)
52
+ # 4.2 workaround
53
+ return value if (type_sym == :string || type_sym == :text) && value.nil?
54
+
55
+ type_cast(value)
56
+ end
57
+
58
+ def type_cast(value, arrayize: true)
59
+ if array && (arrayize || value.is_a?(Array))
60
+ return [] if arrayize && !value.is_a?(Array)
61
+ return value.map { |v| type_cast(v, arrayize: false) }
62
+ end
63
+
64
+ # 4.2 workaround
65
+ if type_sym == :string || type_sym == :text
66
+ return value.to_s unless value.blank? && (null || array)
67
+ end
68
+
69
+ if type.respond_to?(:cast)
70
+ type.cast(value)
71
+ else
72
+ type.type_cast_from_user(value)
73
+ end
74
+ end
75
+ end
76
+ end