active_store_accessor 0.2.1 → 0.3.1
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 +4 -4
- data/README.md +72 -7
- data/active_store_accessor.gemspec +1 -1
- data/lib/active_store_accessor.rb +119 -42
- data/lib/active_store_accessor/builder.rb +21 -0
- data/lib/active_store_accessor/rails.rb +50 -0
- data/lib/active_store_accessor/type.rb +18 -0
- data/tests/lib/active_store_accessor_test.rb +6 -0
- data/tests/test_helper.rb +2 -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: 3b01aee96903d6965851b7fe623f388deeae0bf2
|
4
|
+
data.tar.gz: 46265923f7da2f21da4cac1f6ccdaf164842ec73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e166097b7ad612555e65b6693c6915224bc97a7d0a656349a7c5ba6084178a65399e16126e91a8fafd10a48dd4402edb2815c0e161e3dd2a63b5c87d75be7f89
|
7
|
+
data.tar.gz: 38594357276667ebf2e2728f6cb34b8ef31978beddb79bc95a19e13f8b0b4be00f65c745a593b28e57f995fd014dd696e2ab24c8bafd89f3bb4eb382836dac76
|
data/README.md
CHANGED
@@ -4,15 +4,14 @@
|
|
4
4
|
[](http://badge.fury.io/rb/active_store_accessor)
|
5
5
|
[](https://codeclimate.com/github/jalkoby/active_store_accessor)
|
6
6
|
|
7
|
-
`active_store_accessor` makes work with store accessors more productive. There is no need to cast a serialized attribute to required type(boolean, time, float, etc). Just define it with a tiny wrapper method and everything is done for you.
|
7
|
+
`active_store_accessor` makes a work with store accessors more productive. There is no need to cast a serialized attribute to a required type(boolean, time, float, etc). Just define it with a tiny wrapper method and everything is done for you.
|
8
8
|
|
9
9
|
## Usage
|
10
10
|
|
11
|
-
|
11
|
+
The basic usage:
|
12
12
|
|
13
13
|
```ruby
|
14
14
|
class Profile < ActiveRecord::Base
|
15
|
-
# basic usage(where `info` is a store column)
|
16
15
|
active_store_accessor :info, age: :integer, birthday: :time
|
17
16
|
|
18
17
|
# with default values
|
@@ -30,11 +29,11 @@ profile.score = 4.5
|
|
30
29
|
profile.score # => 4.5
|
31
30
|
```
|
32
31
|
|
33
|
-
|
32
|
+
The extra logic in a property methods:
|
34
33
|
```ruby
|
35
34
|
# Story:
|
36
|
-
# users have a rank, but if
|
37
|
-
# nobody can change
|
35
|
+
# users have a rank, but if a user was locked by admins
|
36
|
+
# nobody can change a rank & it's value should be equal to zero
|
38
37
|
class User
|
39
38
|
active_store_accessor :info, rank: :float
|
40
39
|
|
@@ -49,6 +48,72 @@ class User
|
|
49
48
|
end
|
50
49
|
```
|
51
50
|
|
51
|
+
## Adding a custom type
|
52
|
+
Add a custom type is easy enough:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# using a block
|
56
|
+
ActiveStoreAccessor.add_type(:even) do |builder|
|
57
|
+
builder.to_source { |value| (value.to_i / 2) * 2 }
|
58
|
+
end
|
59
|
+
|
60
|
+
# using a lambda
|
61
|
+
ActiveStoreAccessor.add_type(:even) do |builder|
|
62
|
+
to_source = lambda { |value| (value.to_i / 2) * 2 }
|
63
|
+
builder.to_source(to_source)
|
64
|
+
end
|
65
|
+
|
66
|
+
# using a object with #call method
|
67
|
+
class EvenConvert
|
68
|
+
def call(value)
|
69
|
+
(value.to_i / 2) * 2
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
ActiveStoreAccessor.add_type(:even) do |builder|
|
74
|
+
builder.to_source(EvenConvert.new)
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
Sometimes you need to deserialize your value of a custom type. To do it look at the following example:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
ActiveStoreAccessor.add_type(:point) do |builder|
|
82
|
+
builder.to_source do |value|
|
83
|
+
"#{ value.x },#{ value.y }"
|
84
|
+
end
|
85
|
+
|
86
|
+
builder.from_source do |value|
|
87
|
+
parts = value.split(',')
|
88
|
+
Point.new(parts[0], parts[1])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
There is a common issue when you use `block`-style to define a custom type:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
ActiveStoreAccessor.add_type(:point) |builder|
|
97
|
+
builder.to_source do |value|
|
98
|
+
return unless value.is_a?(Point)
|
99
|
+
# ...
|
100
|
+
end
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
Ruby will rise an error Unexpected Return (LocalJumpError). To avoid it replace a block by a lambda:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
ActiveStoreAccessor.add_type(:point) |builder|
|
108
|
+
to_source = lambda do |value|
|
109
|
+
return unless value.is_a?(Point)
|
110
|
+
# ...
|
111
|
+
end
|
112
|
+
|
113
|
+
builder.to_source(to_source)
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
52
117
|
## Installation
|
53
118
|
|
54
119
|
Add this line to your application's Gemfile:
|
@@ -65,7 +130,7 @@ This library has been tested on ruby 1.9.3+ and activerecord 4.0+.
|
|
65
130
|
|
66
131
|
## Contributing
|
67
132
|
|
68
|
-
1. Fork it ( https://github.com/
|
133
|
+
1. Fork it ( https://github.com/jalkoby/active_store_accessor/fork )
|
69
134
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
70
135
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
71
136
|
4. Push to the branch (`git push origin my-new-feature`)
|
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "active_store_accessor"
|
7
|
-
spec.version = "0.
|
7
|
+
spec.version = "0.3.1"
|
8
8
|
spec.authors = ["Sergey Pchelincev"]
|
9
9
|
spec.email = ["mail@sergeyp.me"]
|
10
10
|
spec.summary = %q{Get more from ActiveRecord::Store}
|
@@ -1,63 +1,140 @@
|
|
1
|
+
require_relative "active_store_accessor/builder"
|
2
|
+
require_relative "active_store_accessor/rails"
|
3
|
+
require_relative "active_store_accessor/type"
|
4
|
+
|
1
5
|
module ActiveStoreAccessor
|
2
|
-
|
3
|
-
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def types
|
9
|
+
@types ||= []
|
10
|
+
end
|
4
11
|
|
5
|
-
|
12
|
+
def add_type(*names)
|
13
|
+
raise_arg_error("You could use only symbols for name") if names.any? { |name| !name.is_a?(Symbol) }
|
14
|
+
builder = Builder.new(names)
|
15
|
+
yield builder
|
16
|
+
types << builder.to_type
|
6
17
|
end
|
7
18
|
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
serialize(column_name) unless serialized_attributes.include?(column_name.to_s)
|
19
|
+
def find_type(name, options, model_name)
|
20
|
+
raise_arg_error("Please use a symbol for a setting type in #{ model_name }.") unless name.is_a?(Symbol)
|
21
|
+
type_args = types.detect { |type_args| type_args[0].include?(name) }
|
22
|
+
unless type_args
|
23
|
+
raise_arg_error("#{ name } is unknown. Please recheck a type's name or add it by `ActiveStoreAccessor.add_type`.")
|
14
24
|
end
|
25
|
+
Type.new(type_args[1], type_args[2], options[:default])
|
26
|
+
end
|
27
|
+
|
28
|
+
def raise_error(klass, message)
|
29
|
+
raise klass, "[active_store_accessor] #{ message }"
|
30
|
+
end
|
15
31
|
|
16
|
-
|
32
|
+
def raise_arg_error(message)
|
33
|
+
raise_error ArgumentError, message
|
34
|
+
end
|
17
35
|
|
18
|
-
|
19
|
-
|
20
|
-
|
36
|
+
def raise_convertion_error(value, type)
|
37
|
+
raise_error klass, "ActiveStoreAccessor doesn't know how to convert #{ value.class } into #{ type }"
|
38
|
+
end
|
39
|
+
|
40
|
+
add_type(:integer) do |builder|
|
41
|
+
builder.to_source { |value| value.to_i rescue nil }
|
42
|
+
end
|
21
43
|
|
22
|
-
|
23
|
-
|
24
|
-
|
44
|
+
add_type(:float, :double) do |builder|
|
45
|
+
builder.to_source { |value| value.to_f unless value.nil? }
|
46
|
+
end
|
47
|
+
|
48
|
+
add_type(:decimal) do |builder|
|
49
|
+
builder.from_source { |value| value.to_d unless value.nil? }
|
50
|
+
|
51
|
+
builder.to_source do |value|
|
52
|
+
if value.respond_to?(:to_d)
|
53
|
+
value.to_d.to_s
|
25
54
|
else
|
26
|
-
|
27
|
-
# but for most rubist and rails developer it should contains a date too
|
28
|
-
type = 'datetime' if type == 'time'
|
29
|
-
args = [attr_name.to_s, options[:default], type]
|
30
|
-
column = ActiveRecord::ConnectionAdapters::Column.new(*args)
|
31
|
-
[column.method(:type_cast), column.method(:type_cast_for_write)]
|
55
|
+
value.to_s.presence
|
32
56
|
end
|
57
|
+
end
|
58
|
+
end
|
33
59
|
|
34
|
-
|
35
|
-
|
60
|
+
# time for activerecord is only a hours and minutes without date part
|
61
|
+
# but for most rubist and rails developer it should contains a date too
|
62
|
+
add_type(:time, :datetime) do |builder|
|
63
|
+
builder.from_source { |value| Time.parse(value) if value }
|
36
64
|
|
37
|
-
|
38
|
-
|
39
|
-
getter, _, default = self.class.active_store_attributes[:#{ attr_name }]
|
40
|
-
value = getter.call(super)
|
41
|
-
value.nil? ? default : value
|
42
|
-
end
|
65
|
+
to_source = lambda do |value|
|
66
|
+
return unless value.present?
|
43
67
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
68
|
+
case value
|
69
|
+
when String then Time.parse(value).to_s
|
70
|
+
when Fixnum then Time.at(value).to_s
|
71
|
+
when Date, Time, DateTime, ActiveSupport::TimeZone then value.to_s
|
72
|
+
else
|
73
|
+
raise_convertion_error(value, "Time")
|
74
|
+
end
|
49
75
|
end
|
76
|
+
|
77
|
+
builder.to_source(to_source)
|
78
|
+
end
|
79
|
+
|
80
|
+
add_type(:date) do |builder|
|
81
|
+
to_source = lambda do |value|
|
82
|
+
return if value.nil?
|
83
|
+
case value
|
84
|
+
when String then Date.parse(value).to_s
|
85
|
+
when Date then value.to_s
|
86
|
+
when Time, DateTime then value.to_date.to_s
|
87
|
+
else
|
88
|
+
raise_convertion_error(value, "Date")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
builder.to_source(to_source)
|
50
93
|
end
|
51
94
|
|
52
|
-
|
95
|
+
add_type(:text, :string, :binary) do |builder|
|
96
|
+
builder.to_source { |value| value.presence }
|
97
|
+
end
|
53
98
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
99
|
+
add_type(:boolean, :bool) do |builder|
|
100
|
+
true_values = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
|
101
|
+
|
102
|
+
builder.to_source do |value|
|
103
|
+
if value.nil? || (value.is_a?(String) && value.empty?)
|
104
|
+
nil
|
105
|
+
else
|
106
|
+
true_values.include?(value)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
add_type(:array, :list) do |builder|
|
112
|
+
builder.to_source { |value| ActiveSupport::JSON.encode(Array(value)) unless value.nil? }
|
113
|
+
builder.from_source { |value| ActiveSupport::JSON.decode(value) unless value.nil? }
|
114
|
+
end
|
115
|
+
|
116
|
+
add_type(:hash, :dictonary) do |builder|
|
117
|
+
if RUBY_VERSION =~ /1\.9/
|
118
|
+
to_source = lambda do |value|
|
119
|
+
return if value.nil?
|
120
|
+
|
121
|
+
value = if value.is_a?(Hash)
|
122
|
+
value
|
123
|
+
elsif value.respond_to?(:to_h)
|
124
|
+
value.to_h
|
125
|
+
elsif value.respond_to?(:to_hash)
|
126
|
+
value.to_hash
|
127
|
+
else
|
128
|
+
raise_convertion_error(value, 'Hash')
|
129
|
+
end
|
130
|
+
ActiveSupport::JSON.encode(value)
|
131
|
+
end
|
132
|
+
builder.to_source(to_source)
|
133
|
+
else
|
134
|
+
builder.to_source { |value| ActiveSupport::JSON.encode(Hash(value)) unless value.nil? }
|
59
135
|
end
|
136
|
+
builder.from_source { |value| ActiveSupport::JSON.decode(value) unless value.nil? }
|
60
137
|
end
|
61
138
|
end
|
62
139
|
|
63
|
-
ActiveSupport.on_load(:active_record) { ActiveRecord::Base.extend(ActiveStoreAccessor) }
|
140
|
+
ActiveSupport.on_load(:active_record) { ActiveRecord::Base.extend(ActiveStoreAccessor::Rails) }
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveStoreAccessor
|
2
|
+
class Builder
|
3
|
+
DEFAULT_CASTER = lambda { |value| value }
|
4
|
+
|
5
|
+
def initialize(names)
|
6
|
+
@names = names
|
7
|
+
end
|
8
|
+
|
9
|
+
def from_source(caster = nil, &block)
|
10
|
+
@from_source = caster || block
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_source(caster = nil, &block)
|
14
|
+
@to_source = caster || block
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_type
|
18
|
+
[@names, (@from_source || DEFAULT_CASTER), (@to_source || DEFAULT_CASTER)]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ActiveStoreAccessor
|
2
|
+
module Rails
|
3
|
+
def active_store_attributes
|
4
|
+
return @active_store_attributes if @active_store_attributes
|
5
|
+
|
6
|
+
@active_store_attributes = superclass.respond_to?(:active_store_attributes) ? superclass.active_store_attributes : {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def active_store_accessor(column_name, attrs)
|
10
|
+
column = columns.detect { |column| column.name == column_name.to_s }
|
11
|
+
if !column
|
12
|
+
::ActiveStoreAccessor.raise_arg_error "The column `#{ column_name }` does not exist in the model #{ name }."
|
13
|
+
elsif column.type == :text
|
14
|
+
serialize(column_name) unless serialized_attributes.include?(column_name.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
store_accessor column_name, *attrs.keys
|
18
|
+
|
19
|
+
attrs.each do |attr_name, options|
|
20
|
+
options = { type: options } unless options.is_a?(Hash)
|
21
|
+
|
22
|
+
type = options.fetch(:type) do
|
23
|
+
::ActiveStoreAccessor.raise_arg_error "Please specify the type of `#{ attr_name }` attribute."
|
24
|
+
end
|
25
|
+
|
26
|
+
active_store_attributes[attr_name] = ::ActiveStoreAccessor.find_type(type, options, name)
|
27
|
+
|
28
|
+
_active_store_accessor_module.module_eval <<-RUBY
|
29
|
+
def #{ attr_name }
|
30
|
+
self.class.active_store_attributes.fetch(:#{ attr_name }).from_store(super)
|
31
|
+
end
|
32
|
+
|
33
|
+
def #{ attr_name }=(value)
|
34
|
+
super self.class.active_store_attributes.fetch(:#{ attr_name }).to_store(value)
|
35
|
+
end
|
36
|
+
RUBY
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def _active_store_accessor_module
|
43
|
+
@_active_store_accessor_module ||= begin
|
44
|
+
mod = Module.new
|
45
|
+
include mod
|
46
|
+
mod
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActiveStoreAccessor
|
2
|
+
class Type
|
3
|
+
def initialize(from, to, default)
|
4
|
+
@from = from
|
5
|
+
@to = to
|
6
|
+
@default = default
|
7
|
+
end
|
8
|
+
|
9
|
+
def from_store(value)
|
10
|
+
result = @from.call(value)
|
11
|
+
result.nil? ? @default : result
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_store(value)
|
15
|
+
@to.call(value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -9,18 +9,24 @@ describe ActiveStoreAccessor do
|
|
9
9
|
assert_equal profile.rank, nil
|
10
10
|
assert_equal profile.birthday, nil
|
11
11
|
assert_equal profile.confirmed, nil
|
12
|
+
assert_equal profile.photos, nil
|
13
|
+
assert_equal profile.scores, nil
|
12
14
|
|
13
15
|
profile.age = "20"
|
14
16
|
profile.score = 100.32
|
15
17
|
profile.rank = "3213.317"
|
16
18
|
profile.birthday = Time.utc(2014, 5, 12)
|
17
19
|
profile.confirmed = "1"
|
20
|
+
profile.photos = 'my_doggy.png'
|
21
|
+
profile.scores = { woo: 2 }
|
18
22
|
|
19
23
|
assert_equal profile.age, 20
|
20
24
|
assert_equal profile.score, 100
|
21
25
|
assert_equal profile.rank, 3213.32
|
22
26
|
assert_equal profile.birthday, Time.utc(2014, 5, 12)
|
23
27
|
assert profile.confirmed
|
28
|
+
assert_equal profile.photos, ['my_doggy.png']
|
29
|
+
assert_equal profile.scores, { 'woo' => 2 }
|
24
30
|
end
|
25
31
|
|
26
32
|
it "should support model inheritance" do
|
data/tests/test_helper.rb
CHANGED
@@ -25,6 +25,8 @@ class Profile < ActiveRecord::Base
|
|
25
25
|
|
26
26
|
active_store_accessor :keys, active: :boolean
|
27
27
|
active_store_accessor :keys, pi: :float
|
28
|
+
active_store_accessor :keys, photos: :list
|
29
|
+
active_store_accessor :keys, scores: :hash
|
28
30
|
|
29
31
|
def rank=(value)
|
30
32
|
super(value)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_store_accessor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Pchelincev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -81,6 +81,9 @@ files:
|
|
81
81
|
- Rakefile
|
82
82
|
- active_store_accessor.gemspec
|
83
83
|
- lib/active_store_accessor.rb
|
84
|
+
- lib/active_store_accessor/builder.rb
|
85
|
+
- lib/active_store_accessor/rails.rb
|
86
|
+
- lib/active_store_accessor/type.rb
|
84
87
|
- tests/lib/active_store_accessor_test.rb
|
85
88
|
- tests/test_helper.rb
|
86
89
|
homepage: ''
|