activerecord-typedstore 0.6.1 → 1.0.0.beta1

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: 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