hstore_accessor 0.5.4 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/hstore_accessor.rb +5 -127
- data/lib/hstore_accessor/macro.rb +76 -0
- data/lib/hstore_accessor/serialization.rb +45 -0
- data/lib/hstore_accessor/time_helper.rb +1 -1
- data/lib/hstore_accessor/type_helpers.rb +20 -0
- data/lib/hstore_accessor/version.rb +1 -1
- data/spec/hstore_accessor_spec.rb +20 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e53358ab1b69b9ae41faf29e52c620348a9241e
|
4
|
+
data.tar.gz: dbccbde5437d1aa332eaa60f0d8d500bd822b720
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69c6b255e86c6f15e96011c3641c0719bc7629927c96d9438368b7daaaf0ea6713393f4b17e69fa3d12bd12dac7a66738c5bb1b7c5027b822d5d1bafbe5274ca
|
7
|
+
data.tar.gz: dd2450efa79dcac2f941348471d8775207b0f6682a7f531025c6c08d505170d21cea39cfb4e7e4dca71db311244292fe7bb9048e94e556661e26553d3bcf228e
|
data/lib/hstore_accessor.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
require "hstore_accessor/version"
|
2
|
+
require "hstore_accessor/type_helpers"
|
3
|
+
require "hstore_accessor/serialization"
|
4
|
+
require "hstore_accessor/macro"
|
2
5
|
require "hstore_accessor/time_helper"
|
3
6
|
require "active_support"
|
4
7
|
require "active_record"
|
@@ -6,133 +9,8 @@ require "bigdecimal"
|
|
6
9
|
|
7
10
|
module HstoreAccessor
|
8
11
|
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
VALID_TYPES = [:string, :integer, :float, :time, :boolean, :array, :hash, :date, :decimal]
|
13
|
-
|
14
|
-
SEPARATOR = "||;||"
|
15
|
-
|
16
|
-
DEFAULT_SERIALIZER = ->(value) { value.to_s }
|
17
|
-
DEFAULT_DESERIALIZER = DEFAULT_SERIALIZER
|
18
|
-
|
19
|
-
SERIALIZERS = {
|
20
|
-
array: -> value { (value && value.join(SEPARATOR)) || nil },
|
21
|
-
hash: -> value { (value && value.to_json) || nil },
|
22
|
-
time: -> value { value.to_i },
|
23
|
-
boolean: -> value { (value.to_s == "true").to_s },
|
24
|
-
date: -> value { (value && value.to_s) || nil }
|
25
|
-
}
|
26
|
-
|
27
|
-
DESERIALIZERS = {
|
28
|
-
array: -> value { (value && value.split(SEPARATOR)) || nil },
|
29
|
-
hash: -> value { (value && JSON.parse(value)) || nil },
|
30
|
-
integer: -> value { value.to_i },
|
31
|
-
float: -> value { value.to_f },
|
32
|
-
time: -> value { Time.at(value.to_i) },
|
33
|
-
boolean: -> value { value == "true" },
|
34
|
-
date: -> value { (value && Date.parse(value)) || nil },
|
35
|
-
decimal: -> value { BigDecimal.new(value) }
|
36
|
-
}
|
37
|
-
|
38
|
-
def serialize(type, value, serializer=nil)
|
39
|
-
return nil if value.nil?
|
40
|
-
serializer ||= (SERIALIZERS[type] || DEFAULT_SERIALIZER)
|
41
|
-
serializer.call(value)
|
42
|
-
end
|
43
|
-
|
44
|
-
def deserialize(type, value, deserializer=nil)
|
45
|
-
return nil if value.nil?
|
46
|
-
deserializer ||= (DESERIALIZERS[type] || DEFAULT_DESERIALIZER)
|
47
|
-
deserializer.call(value)
|
48
|
-
end
|
49
|
-
|
50
|
-
def type_cast(type, value)
|
51
|
-
return nil if value.nil?
|
52
|
-
column_class = ActiveRecord::ConnectionAdapters::Column
|
53
|
-
case type
|
54
|
-
when :string,:hash,:array,
|
55
|
-
:decimal then value
|
56
|
-
when :integer then column_class.value_to_integer(value)
|
57
|
-
when :float then value.to_f
|
58
|
-
when :time then TimeHelper.string_to_time(value)
|
59
|
-
when :date then column_class.value_to_date(value)
|
60
|
-
when :boolean then column_class.value_to_boolean(value)
|
61
|
-
else value
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
module ClassMethods
|
66
|
-
|
67
|
-
def hstore_accessor(hstore_attribute, fields)
|
68
|
-
fields.each do |key, type|
|
69
|
-
|
70
|
-
data_type = type
|
71
|
-
store_key = key
|
72
|
-
if type.is_a?(Hash)
|
73
|
-
type = type.with_indifferent_access
|
74
|
-
data_type = type[:data_type]
|
75
|
-
store_key = type[:store_key]
|
76
|
-
end
|
77
|
-
|
78
|
-
data_type = data_type.to_sym
|
79
|
-
|
80
|
-
raise InvalidDataTypeError unless VALID_TYPES.include?(data_type)
|
81
|
-
|
82
|
-
define_method("hstore_metadata_for_#{hstore_attribute}") do
|
83
|
-
fields
|
84
|
-
end
|
85
|
-
|
86
|
-
define_method("#{key}=") do |value|
|
87
|
-
send("#{hstore_attribute}=", (send(hstore_attribute) || {}).merge(store_key.to_s => serialize(data_type, type_cast(type, value))))
|
88
|
-
send("#{hstore_attribute}_will_change!")
|
89
|
-
end
|
90
|
-
|
91
|
-
define_method(key) do
|
92
|
-
value = send(hstore_attribute) && send(hstore_attribute).with_indifferent_access[store_key.to_s]
|
93
|
-
deserialize(data_type, value)
|
94
|
-
end
|
95
|
-
|
96
|
-
if type == :boolean
|
97
|
-
define_method("#{key}?") do
|
98
|
-
return send("#{key}")
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
query_field = "#{hstore_attribute} -> '#{store_key}'"
|
103
|
-
|
104
|
-
case data_type
|
105
|
-
when :string
|
106
|
-
send(:scope, "with_#{key}", -> value { where("#{query_field} = ?", value.to_s) })
|
107
|
-
when :integer, :float, :decimal
|
108
|
-
send(:scope, "#{key}_lt", -> value { where("(#{query_field})::#{data_type} < ?", value.to_s) })
|
109
|
-
send(:scope, "#{key}_lte", -> value { where("(#{query_field})::#{data_type} <= ?", value.to_s) })
|
110
|
-
send(:scope, "#{key}_eq", -> value { where("(#{query_field})::#{data_type} = ?", value.to_s) })
|
111
|
-
send(:scope, "#{key}_gte", -> value { where("(#{query_field})::#{data_type} >= ?", value.to_s) })
|
112
|
-
send(:scope, "#{key}_gt", -> value { where("(#{query_field})::#{data_type} > ?", value.to_s) })
|
113
|
-
when :time
|
114
|
-
send(:scope, "#{key}_before", -> value { where("(#{query_field})::integer < ?", value.to_i) })
|
115
|
-
send(:scope, "#{key}_eq", -> value { where("(#{query_field})::integer = ?", value.to_i) })
|
116
|
-
send(:scope, "#{key}_after", -> value { where("(#{query_field})::integer > ?", value.to_i) })
|
117
|
-
when :date
|
118
|
-
send(:scope, "#{key}_before", -> value { where("#{query_field} < ?", value.to_s) })
|
119
|
-
send(:scope, "#{key}_eq", -> value { where("#{query_field} = ?", value.to_s) })
|
120
|
-
send(:scope, "#{key}_after", -> value { where("#{query_field} > ?", value.to_s) })
|
121
|
-
when :boolean
|
122
|
-
send(:scope, "is_#{key}", -> { where("#{query_field} = 'true'") })
|
123
|
-
send(:scope, "not_#{key}", -> { where("#{query_field} = 'false'") })
|
124
|
-
when :array
|
125
|
-
send(:scope, "#{key}_eq", -> value { where("#{query_field} = ?", value.join(SEPARATOR)) })
|
126
|
-
send(:scope, "#{key}_contains", -> value do
|
127
|
-
where("string_to_array(#{query_field}, '#{SEPARATOR}') @> string_to_array(?, '#{SEPARATOR}')", Array[value].flatten)
|
128
|
-
end)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
end
|
133
|
-
|
134
|
-
end
|
135
|
-
|
12
|
+
include Serialization
|
13
|
+
include Macro
|
136
14
|
end
|
137
15
|
|
138
16
|
ActiveSupport.on_load(:active_record) do
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module HstoreAccessor
|
2
|
+
module Macro
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
def hstore_accessor(hstore_attribute, fields)
|
7
|
+
fields.each do |key, type|
|
8
|
+
|
9
|
+
data_type = type
|
10
|
+
store_key = key
|
11
|
+
if type.is_a?(Hash)
|
12
|
+
type = type.with_indifferent_access
|
13
|
+
data_type = type[:data_type]
|
14
|
+
store_key = type[:store_key]
|
15
|
+
end
|
16
|
+
|
17
|
+
data_type = data_type.to_sym
|
18
|
+
|
19
|
+
raise Serialization::InvalidDataTypeError unless Serialization::VALID_TYPES.include?(data_type)
|
20
|
+
|
21
|
+
define_method("hstore_metadata_for_#{hstore_attribute}") do
|
22
|
+
fields
|
23
|
+
end
|
24
|
+
|
25
|
+
define_method("#{key}=") do |value|
|
26
|
+
send("#{hstore_attribute}=", (send(hstore_attribute) || {}).merge(store_key.to_s => serialize(data_type, TypeHelpers.cast(type, value))))
|
27
|
+
send("#{hstore_attribute}_will_change!")
|
28
|
+
end
|
29
|
+
|
30
|
+
define_method(key) do
|
31
|
+
value = send(hstore_attribute) && send(hstore_attribute).with_indifferent_access[store_key.to_s]
|
32
|
+
deserialize(data_type, value)
|
33
|
+
end
|
34
|
+
|
35
|
+
if type == :boolean
|
36
|
+
define_method("#{key}?") do
|
37
|
+
return send("#{key}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
query_field = "#{hstore_attribute} -> '#{store_key}'"
|
42
|
+
|
43
|
+
case data_type
|
44
|
+
when :string
|
45
|
+
send(:scope, "with_#{key}", -> value { where("#{query_field} = ?", value.to_s) })
|
46
|
+
when :integer, :float, :decimal
|
47
|
+
send(:scope, "#{key}_lt", -> value { where("(#{query_field})::#{data_type} < ?", value.to_s) })
|
48
|
+
send(:scope, "#{key}_lte", -> value { where("(#{query_field})::#{data_type} <= ?", value.to_s) })
|
49
|
+
send(:scope, "#{key}_eq", -> value { where("(#{query_field})::#{data_type} = ?", value.to_s) })
|
50
|
+
send(:scope, "#{key}_gte", -> value { where("(#{query_field})::#{data_type} >= ?", value.to_s) })
|
51
|
+
send(:scope, "#{key}_gt", -> value { where("(#{query_field})::#{data_type} > ?", value.to_s) })
|
52
|
+
when :time
|
53
|
+
send(:scope, "#{key}_before", -> value { where("(#{query_field})::integer < ?", value.to_i) })
|
54
|
+
send(:scope, "#{key}_eq", -> value { where("(#{query_field})::integer = ?", value.to_i) })
|
55
|
+
send(:scope, "#{key}_after", -> value { where("(#{query_field})::integer > ?", value.to_i) })
|
56
|
+
when :date
|
57
|
+
send(:scope, "#{key}_before", -> value { where("#{query_field} < ?", value.to_s) })
|
58
|
+
send(:scope, "#{key}_eq", -> value { where("#{query_field} = ?", value.to_s) })
|
59
|
+
send(:scope, "#{key}_after", -> value { where("#{query_field} > ?", value.to_s) })
|
60
|
+
when :boolean
|
61
|
+
send(:scope, "is_#{key}", -> { where("#{query_field} = 'true'") })
|
62
|
+
send(:scope, "not_#{key}", -> { where("#{query_field} = 'false'") })
|
63
|
+
when :array
|
64
|
+
send(:scope, "#{key}_eq", -> value { where("#{query_field} = ?", value.join(Serialization::SEPARATOR)) })
|
65
|
+
send(:scope, "#{key}_contains", -> value do
|
66
|
+
where("string_to_array(#{query_field}, '#{Serialization::SEPARATOR}') @> string_to_array(?, '#{Serialization::SEPARATOR}')", Array[value].flatten)
|
67
|
+
end)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module HstoreAccessor
|
2
|
+
module Serialization
|
3
|
+
|
4
|
+
InvalidDataTypeError = Class.new(StandardError)
|
5
|
+
|
6
|
+
VALID_TYPES = [:string, :integer, :float, :time, :boolean, :array, :hash, :date, :decimal]
|
7
|
+
|
8
|
+
SEPARATOR = "||;||"
|
9
|
+
|
10
|
+
DEFAULT_SERIALIZER = ->(value) { value.to_s }
|
11
|
+
DEFAULT_DESERIALIZER = DEFAULT_SERIALIZER
|
12
|
+
|
13
|
+
SERIALIZERS = {
|
14
|
+
array: -> value { (value && value.join(SEPARATOR)) || nil },
|
15
|
+
hash: -> value { (value && value.to_json) || nil },
|
16
|
+
time: -> value { value.to_i },
|
17
|
+
boolean: -> value { (value.to_s == "true").to_s },
|
18
|
+
date: -> value { (value && value.to_s) || nil }
|
19
|
+
}
|
20
|
+
|
21
|
+
DESERIALIZERS = {
|
22
|
+
array: -> value { (value && value.split(SEPARATOR)) || nil },
|
23
|
+
hash: -> value { (value && JSON.parse(value)) || nil },
|
24
|
+
integer: -> value { value.to_i },
|
25
|
+
float: -> value { value.to_f },
|
26
|
+
time: -> value { Time.at(value.to_i) },
|
27
|
+
boolean: -> value { TypeHelpers.cast(:boolean, value) },
|
28
|
+
date: -> value { (value && Date.parse(value)) || nil },
|
29
|
+
decimal: -> value { BigDecimal.new(value) }
|
30
|
+
}
|
31
|
+
|
32
|
+
def serialize(type, value, serializer=nil)
|
33
|
+
return nil if value.nil?
|
34
|
+
serializer ||= (SERIALIZERS[type] || DEFAULT_SERIALIZER)
|
35
|
+
serializer.call(value)
|
36
|
+
end
|
37
|
+
|
38
|
+
def deserialize(type, value, deserializer=nil)
|
39
|
+
return nil if value.nil?
|
40
|
+
deserializer ||= (DESERIALIZERS[type] || DEFAULT_DESERIALIZER)
|
41
|
+
deserializer.call(value)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module HstoreAccessor
|
2
2
|
module TimeHelper
|
3
3
|
|
4
|
-
# There is a bug in ActiveRecord::ConnectionAdapters::Column#string_to_time
|
4
|
+
# There is a bug in ActiveRecord::ConnectionAdapters::Column#string_to_time
|
5
5
|
# which drops the timezone. This has been fixed, but not released.
|
6
6
|
# This method includes the fix. See: https://github.com/rails/rails/pull/12290
|
7
7
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module HstoreAccessor
|
2
|
+
module TypeHelpers
|
3
|
+
|
4
|
+
def self.cast(type, value)
|
5
|
+
return nil if value.nil?
|
6
|
+
column_class = ActiveRecord::ConnectionAdapters::Column
|
7
|
+
case type
|
8
|
+
when :string,:hash,:array,
|
9
|
+
:decimal then value
|
10
|
+
when :integer then column_class.value_to_integer(value)
|
11
|
+
when :float then value.to_f
|
12
|
+
when :time then TimeHelper.string_to_time(value)
|
13
|
+
when :date then column_class.value_to_date(value)
|
14
|
+
when :boolean then column_class.value_to_boolean(value)
|
15
|
+
else value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -104,6 +104,26 @@ describe HstoreAccessor do
|
|
104
104
|
expect(product.popular?).to be_false
|
105
105
|
end
|
106
106
|
|
107
|
+
it "return true for boolean field set via hash using real boolean" do
|
108
|
+
product.options = { "popular" => true }
|
109
|
+
expect(product.popular?).to be_true
|
110
|
+
end
|
111
|
+
|
112
|
+
it "return false for boolean field set via hash using real boolean" do
|
113
|
+
product.options = { "popular" => false }
|
114
|
+
expect(product.popular?).to be_false
|
115
|
+
end
|
116
|
+
|
117
|
+
it "return true for boolean field set via hash using string" do
|
118
|
+
product.options = { "popular" => "true" }
|
119
|
+
expect(product.popular?).to be_true
|
120
|
+
end
|
121
|
+
|
122
|
+
it "return false for boolean field set via hash using string" do
|
123
|
+
product.options = { "popular" => "false" }
|
124
|
+
expect(product.popular?).to be_false
|
125
|
+
end
|
126
|
+
|
107
127
|
end
|
108
128
|
|
109
129
|
describe "scopes" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hstore_accessor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Hirn
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-03-
|
13
|
+
date: 2014-03-22 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -126,7 +126,10 @@ files:
|
|
126
126
|
- Rakefile
|
127
127
|
- hstore_accessor.gemspec
|
128
128
|
- lib/hstore_accessor.rb
|
129
|
+
- lib/hstore_accessor/macro.rb
|
130
|
+
- lib/hstore_accessor/serialization.rb
|
129
131
|
- lib/hstore_accessor/time_helper.rb
|
132
|
+
- lib/hstore_accessor/type_helpers.rb
|
130
133
|
- lib/hstore_accessor/version.rb
|
131
134
|
- spec/hstore_accessor_spec.rb
|
132
135
|
- spec/spec_helper.rb
|