hattr 1.0.0
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 +7 -0
- data/.coveralls.yml +1 -0
- data/.editorconfig +12 -0
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +17 -0
- data/.travis.yml +12 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +136 -0
- data/Rakefile +29 -0
- data/hattr.gemspec +30 -0
- data/lib/hattr.rb +17 -0
- data/lib/hattr/class_methods.rb +59 -0
- data/lib/hattr/hash_builder.rb +58 -0
- data/lib/hattr/version.rb +4 -0
- data/spec/integration/hash_output_spec.rb +11 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/unit/hattr/class_methods_spec.rb +125 -0
- data/spec/unit/hattr/hash_builder_spec.rb +130 -0
- data/spec/unit/hattr_spec.rb +15 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5dd222e1b2ce862fa5536322fb3f74bfd5d4f595
|
4
|
+
data.tar.gz: b4d8981ec1dde35e7f75063d3d60ee2ee3d3fb0f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bd01418591458c927dcb4c231bac87f8c6cbc4acd29087e119801020266f71fe82c0a33a07c23d7240767b433a1a89f72bbac8051cebb3da69f327509ba15dba
|
7
|
+
data.tar.gz: 799147fd6d7ff561f6737208655641faee1cddad1293acfdcef9fb525d7a70315c69e6684967e049f389a784b70b9e6573191cf30eda3239d749e054704b6324
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.editorconfig
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2016-05-20 15:38:35 -0400 using RuboCop version 0.39.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 5
|
10
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
|
11
|
+
# URISchemes: http, https
|
12
|
+
Metrics/LineLength:
|
13
|
+
Max: 108
|
14
|
+
|
15
|
+
# Offense count: 3
|
16
|
+
Style/Documentation:
|
17
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 Andrew Rempe and contributors.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
Hattr
|
2
|
+
=====
|
3
|
+
|
4
|
+
[](https://travis-ci.org/arempe93/hattr)
|
5
|
+
[](https://rubygems.org/gems/hattr)
|
6
|
+
[](https://coveralls.io/github/arempe93/hattr?branch=master)
|
7
|
+
|
8
|
+
A simple library for interacting with the PSQL `hstore` extension in `ActiveRecord`. Hattr supports coercion of `hstore` attributes based on a simple dsl and will eventually support more advanced features such as Rails validations and more configurable options.
|
9
|
+
|
10
|
+
## Basic Usage
|
11
|
+
|
12
|
+
Hattr was designed to be simple. All you need is to extend the `Hattr` module and start building your spec.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class Television < ActiveRecord::Base
|
16
|
+
extend Hattr
|
17
|
+
|
18
|
+
hattr :metadata, :weight, type: Float
|
19
|
+
hattr :metadata, :screen_class, type: Integer
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
Now when you call `metadata` on your `Television` object, instead of getting an ugly hash back that looks like this:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
{ "weight" => "35.6", "screen_class" => "55" }
|
27
|
+
```
|
28
|
+
|
29
|
+
You can get back data that matches your specification - like this:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
{ weight: 35.6, screen_class: 55 }
|
33
|
+
```
|
34
|
+
|
35
|
+
## Advanced Usage
|
36
|
+
|
37
|
+
While Hattr was designed to be simple, it was also meant to be flexible to many use cases.
|
38
|
+
|
39
|
+
### Attribute Options
|
40
|
+
|
41
|
+
#### :type
|
42
|
+
|
43
|
+
Sets the type of the attribute which defaults to `String` if not specified. Valid values include `String`, `Integer`, `Fixnum`, `Float`, `Symbol`, `Array`, and `Hash`. Errors may be raised if coercion cannot be completed
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class Person < ActiveRecord::Base
|
47
|
+
extend Hattr
|
48
|
+
|
49
|
+
hattr :metadata, :name # :type == String
|
50
|
+
end
|
51
|
+
```
|
52
|
+
**Super advanced usage**
|
53
|
+
|
54
|
+
For `Array` and `Hash` types, you can specify the types of the keys/values. For example, for an array of numbers and a hash of strings mapped to floats, you can do:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class Person < ActiveRecord::Base
|
58
|
+
extend Hattr
|
59
|
+
|
60
|
+
hattr :metadata, :array, type: Array[Fixnum]
|
61
|
+
hattr :metadata, :hash, type: Hash[String => Float]
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
The inner values are subject to inclusion in the list above for normal types.
|
66
|
+
|
67
|
+
**NOT ADVANCED ENOUGH**
|
68
|
+
|
69
|
+
Since it recursively calls `typecast` on the values of the array and **values** of the hash, you can do some pretty crazy stuff
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class Person < ActiveRecord::Base
|
73
|
+
extend Hattr
|
74
|
+
|
75
|
+
hattr :metadata, :wtf, type: Hash[Symbol => Array[Hash[String => Hash[Symbol => Float]]]]
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
Also, in case you're curious, just plain `Hash` and `Array` in the "normal" types list map to `Hash[Symbol => String]` and `Array[String]` respectively behind the scenes.
|
80
|
+
|
81
|
+
### Group Options
|
82
|
+
|
83
|
+
#### :string_keys
|
84
|
+
|
85
|
+
Leaves hash keys as strings instead of symbolizing them. Defaults to `false` if `hattr_group` is not called or `:string_keys` is not specified.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
class Television < ActiveRecord::Base
|
89
|
+
extend Hattr
|
90
|
+
|
91
|
+
hattr_group :metadata, string_keys: true
|
92
|
+
hattr :metadata, :weight, type: Float
|
93
|
+
hattr :metadata, :screen_class, type: Integer
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
### Multiple Attributes
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
class Person < ActiveRecord::Base
|
101
|
+
extend Hattr
|
102
|
+
|
103
|
+
hattr :address, :line1, type: String
|
104
|
+
hattr :address, :line2, type: String
|
105
|
+
hattr :address, :zip, type: Integer
|
106
|
+
|
107
|
+
hattr :metadata, :age, type: Integer
|
108
|
+
hattr :metadata, :weight, type: Float
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
## Contributing
|
113
|
+
|
114
|
+
I would like to add more options to attribute and group dealing with validation, but if you have an idea feel free to open an issue/pull request!
|
115
|
+
|
116
|
+
## Installation
|
117
|
+
|
118
|
+
#### With RubyGems
|
119
|
+
|
120
|
+
To install Hattr with RubyGems:
|
121
|
+
|
122
|
+
```
|
123
|
+
gem install hattr
|
124
|
+
```
|
125
|
+
|
126
|
+
#### With Bundler
|
127
|
+
|
128
|
+
To use Hattr with a Bundler managed project:
|
129
|
+
|
130
|
+
```
|
131
|
+
gem 'hattr'
|
132
|
+
```
|
133
|
+
|
134
|
+
## License
|
135
|
+
|
136
|
+
Released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup :default, :test, :development
|
4
|
+
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
9
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
13
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
14
|
+
spec.rcov = true
|
15
|
+
end
|
16
|
+
|
17
|
+
task :spec
|
18
|
+
|
19
|
+
require 'rubocop/rake_task'
|
20
|
+
RuboCop::RakeTask.new
|
21
|
+
|
22
|
+
task default: [:rubocop, :spec]
|
23
|
+
|
24
|
+
require 'yard'
|
25
|
+
DOC_FILES = ['lib/**/*.rb', 'README.md']
|
26
|
+
|
27
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
28
|
+
t.files = DOC_FILES
|
29
|
+
end
|
data/hattr.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env gem build
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'base64'
|
5
|
+
require File.expand_path("../lib/hattr/version", __FILE__)
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = 'hattr'
|
9
|
+
s.version = Hattr::VERSION.dup
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.authors = ['Andrew Rempe']
|
12
|
+
s.email = [Base64.decode64('YW5kcmV3cmVtcGVAZ21haWwuY29t\n')]
|
13
|
+
s.summary = 'PSQL hstore attributes'
|
14
|
+
s.description = 'A translation for the string centric hstore extension'
|
15
|
+
s.license = 'MIT'
|
16
|
+
|
17
|
+
s.required_ruby_version = Gem::Requirement.new '>= 1.9.3'
|
18
|
+
|
19
|
+
s.add_dependency 'activesupport', '>= 3.0.0'
|
20
|
+
|
21
|
+
s.add_development_dependency 'rake', '~> 10.5.0'
|
22
|
+
s.add_development_dependency 'rubocop'
|
23
|
+
s.add_development_dependency 'yard'
|
24
|
+
s.add_development_dependency 'rspec', '~> 3.4.0'
|
25
|
+
s.add_development_dependency 'coveralls'
|
26
|
+
|
27
|
+
s.files = `git ls-files`.split "\n"
|
28
|
+
s.test_files = `git ls-files -- spec/*`.split "\n"
|
29
|
+
s.require_paths = [ 'lib' ]
|
30
|
+
end
|
data/lib/hattr.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'active_support/core_ext/class/attribute'
|
3
|
+
require 'active_support/core_ext/hash/keys'
|
4
|
+
|
5
|
+
require 'hattr/version'
|
6
|
+
|
7
|
+
require 'hattr/class_methods'
|
8
|
+
require 'hattr/hash_builder'
|
9
|
+
|
10
|
+
module Hattr
|
11
|
+
def self.extended(receiver)
|
12
|
+
receiver.class_attribute :hattr_groups, instance_reader: false, instance_writer: false
|
13
|
+
receiver.hattr_groups = {}
|
14
|
+
|
15
|
+
receiver.extend ClassMethods
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hattr
|
3
|
+
module ClassMethods
|
4
|
+
GROUP_OPTIONS = [:string_keys].freeze
|
5
|
+
ATTR_OPTIONS = [:type].freeze
|
6
|
+
|
7
|
+
GROUP_DEFAULTS = { string_keys: false }.freeze
|
8
|
+
ATTR_DEFAULTS = { type: String }.freeze
|
9
|
+
|
10
|
+
OPTIONS_STORAGE_KEY = :_hattr_options
|
11
|
+
|
12
|
+
ARRAY_DEFAULT = Array[String]
|
13
|
+
HASH_DEFAULT = Hash[Symbol => String]
|
14
|
+
|
15
|
+
def hattr_group(field, opts = {})
|
16
|
+
validate_options(opts, GROUP_OPTIONS)
|
17
|
+
store_group(field, GROUP_DEFAULTS.merge(opts))
|
18
|
+
end
|
19
|
+
|
20
|
+
def hattr(field, attribute, opts = {})
|
21
|
+
raise ArgumentError, "`#{OPTIONS_STORAGE_KEY}` is reserved" if attribute.to_sym == OPTIONS_STORAGE_KEY
|
22
|
+
|
23
|
+
validate_options(opts, ATTR_OPTIONS)
|
24
|
+
store_attribute(field, attribute, ATTR_DEFAULTS.merge(opts))
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_group_hash(spec, value)
|
28
|
+
HashBuilder.generate(spec, value)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def validate_options(opts, valid_keys)
|
34
|
+
opts.keys.each do |k|
|
35
|
+
unless valid_keys.include?(k)
|
36
|
+
raise ArgumentError, "invalid option `#{k}`. Valid options include: :#{valid_keys.join(', :')}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def store_group(field, opts)
|
42
|
+
hattr_groups[field.to_sym] = { OPTIONS_STORAGE_KEY => opts }
|
43
|
+
create_reader_method(field)
|
44
|
+
end
|
45
|
+
|
46
|
+
def store_attribute(field, attribute, opts)
|
47
|
+
store_group(field, GROUP_DEFAULTS.dup) unless hattr_groups[field]
|
48
|
+
hattr_groups[field][attribute.to_sym] = opts
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_reader_method(field)
|
52
|
+
class_eval do
|
53
|
+
define_method field do
|
54
|
+
self.class.build_group_hash(self.class.hattr_groups[field].dup, read_attribute(field))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hattr
|
3
|
+
module HashBuilder
|
4
|
+
module_function
|
5
|
+
|
6
|
+
ARRAY_TYPE_DEFAULT = Array[String]
|
7
|
+
HASH_TYPE_DEFAULT = Hash[Symbol => String]
|
8
|
+
|
9
|
+
def generate(spec, raw)
|
10
|
+
opts = spec.delete(ClassMethods::OPTIONS_STORAGE_KEY)
|
11
|
+
|
12
|
+
raw = raw.symbolize_keys unless opts[:string_keys]
|
13
|
+
raw.each { |k, v| raw[k] = typecast(v, spec.fetch(k.to_sym, ClassMethods::ATTR_DEFAULTS)[:type]) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def typecast(value, type)
|
17
|
+
case
|
18
|
+
when type.class == Hash then hash_typecast(value, type)
|
19
|
+
when type.class == Array then array_typecast(value, type)
|
20
|
+
else primitive_typecast(value, type)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def primitive_typecast(value, type)
|
25
|
+
case
|
26
|
+
when type == Integer, type == Fixnum then value.to_i
|
27
|
+
when type == Float then value.to_f
|
28
|
+
when type == Symbol then value.to_sym
|
29
|
+
when type == Array then array_typecast(value, ARRAY_TYPE_DEFAULT)
|
30
|
+
when type == Hash then hash_typecast(value, HASH_TYPE_DEFAULT)
|
31
|
+
else value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def hash_typecast(value, model)
|
36
|
+
key_type, val_type = model.flatten
|
37
|
+
hash = string_to_hash(value)
|
38
|
+
|
39
|
+
hash = key_type == Symbol ? hash.symbolize_keys : hash
|
40
|
+
hash.each { |k, v| hash[k] = typecast(v, val_type) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def array_typecast(value, model)
|
44
|
+
val_type = model.first
|
45
|
+
array = string_to_array(value)
|
46
|
+
|
47
|
+
array.map { |elem| typecast(elem, val_type) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def string_to_hash(str)
|
51
|
+
Hash[str.tr('{}:" ', '').split(',').map { |pair| pair.split('=>') }]
|
52
|
+
end
|
53
|
+
|
54
|
+
def string_to_array(str)
|
55
|
+
str.tr('[]:" ', '').split(',')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
describe Hattr::HashBuilder, '#generate' do
|
2
|
+
subject { described_class }
|
3
|
+
|
4
|
+
it 'simple example' do
|
5
|
+
spec = { _hattr_options: Hattr::ClassMethods::GROUP_DEFAULTS, int: { type: Integer }, arr: { type: Array }, hsh: { type: Hash[Symbol => Float] } }
|
6
|
+
raw = { 'int' => '123', arr: ['one', 'two', 'three'].to_s, hsh: { 'one' => '1.0', 'two' => '2.0', 'three' => '3.0' }.to_s }
|
7
|
+
output = { int: 123, arr: ['one', 'two', 'three'], hsh: { one: 1.0, two: 2.0, three: 3.0 } }
|
8
|
+
|
9
|
+
expect(subject.generate(spec, raw)).to eql output
|
10
|
+
end
|
11
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'bundler'
|
6
|
+
Bundler.setup :default, :test
|
7
|
+
Bundler.require
|
8
|
+
|
9
|
+
require 'coveralls'
|
10
|
+
Coveralls.wear!
|
11
|
+
|
12
|
+
require 'hattr'
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
describe Hattr::ClassMethods do
|
2
|
+
subject { Class.new.extend(Hattr) }
|
3
|
+
|
4
|
+
context '::hattr_group' do
|
5
|
+
let(:field) { :group }
|
6
|
+
let(:options) { Hash.new }
|
7
|
+
after { subject.hattr_group(field, options) }
|
8
|
+
|
9
|
+
it 'should validate group level options' do
|
10
|
+
expect(subject).to receive(:validate_options).with(options, described_class::GROUP_OPTIONS)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should store group' do
|
14
|
+
expect(subject).to receive(:store_group).with(field, described_class::GROUP_DEFAULTS.merge(options))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context '::hattr' do
|
19
|
+
let(:field) { :group }
|
20
|
+
let(:attribute) { :attr }
|
21
|
+
let(:options) { Hash.new }
|
22
|
+
|
23
|
+
context 'when using reserved attribute name' do
|
24
|
+
let(:attribute) { described_class::OPTIONS_STORAGE_KEY }
|
25
|
+
|
26
|
+
it 'should raise ArgumentError' do
|
27
|
+
expect { subject.hattr(field, attribute, options) }.to raise_error ArgumentError
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when valid input provided' do
|
32
|
+
after { subject.hattr(field, attribute, options) }
|
33
|
+
|
34
|
+
it 'should validate attribute level options' do
|
35
|
+
expect(subject).to receive(:validate_options).with(options, described_class::ATTR_OPTIONS)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should store attribute' do
|
39
|
+
expect(subject).to receive(:store_attribute).with(field, attribute, described_class::ATTR_DEFAULTS.merge(options))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context '::validate_options' do
|
45
|
+
let(:options) { described_class::GROUP_DEFAULTS }
|
46
|
+
|
47
|
+
context 'when invalid keys given' do
|
48
|
+
let(:options) { Hash[foo: :bar] }
|
49
|
+
|
50
|
+
it 'should raise ArgumentError' do
|
51
|
+
expect { subject.send(:validate_options, options, described_class::GROUP_OPTIONS) }.to raise_error ArgumentError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when all keys are valid' do
|
56
|
+
it 'should not raise error' do
|
57
|
+
expect { subject.send(:validate_options, options, described_class::GROUP_OPTIONS) }.to_not raise_error
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context '::store_group' do
|
63
|
+
let(:field) { :group }
|
64
|
+
let(:options) { Hash[foo: :bar] }
|
65
|
+
before { subject.send(:store_group, field, options) }
|
66
|
+
after { subject.send(:store_group, field, options) }
|
67
|
+
|
68
|
+
it 'should store options in `hattr_groups`' do
|
69
|
+
expect(subject.hattr_groups[field]).to eql Hash[described_class::OPTIONS_STORAGE_KEY => options]
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should create reader method for field' do
|
73
|
+
expect(subject).to receive(:create_reader_method).with(field)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context '::store_attribute' do
|
78
|
+
let(:field) { :group }
|
79
|
+
let(:attribute) { :attr }
|
80
|
+
let(:options) { Hash[foo: :bar] }
|
81
|
+
|
82
|
+
context 'when group not yet defined' do
|
83
|
+
after { subject.send(:store_attribute, field, attribute, options) }
|
84
|
+
|
85
|
+
it 'should store group with default options' do
|
86
|
+
expect(subject).to receive(:store_group).with(field, described_class::GROUP_DEFAULTS) do |field, opts|
|
87
|
+
subject.hattr_groups[field] = { described_class::OPTIONS_STORAGE_KEY => opts.dup }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when group already defined' do
|
93
|
+
before { subject.hattr_groups[field] = { described_class::OPTIONS_STORAGE_KEY => described_class::GROUP_DEFAULTS } }
|
94
|
+
after { subject.send(:store_attribute, field, attribute, options) }
|
95
|
+
|
96
|
+
it 'should not store group again' do
|
97
|
+
expect(subject).to_not receive(:store_group)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should store options in group' do
|
102
|
+
subject.send(:store_attribute, field, attribute, options)
|
103
|
+
expect(subject.hattr_groups[field][attribute]).to eql options
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context '::create_reader_method' do
|
108
|
+
let(:field) { :group }
|
109
|
+
before { subject.send(:create_reader_method, field) }
|
110
|
+
|
111
|
+
it 'should add instance method with name field' do
|
112
|
+
expect(subject.new).to respond_to field
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context '::build_group_hash' do
|
117
|
+
let(:spec) { Hash[foo: :bar] }
|
118
|
+
let(:value) { Hash.new[boo: :baz]}
|
119
|
+
after { subject.send(:build_group_hash, spec, value) }
|
120
|
+
|
121
|
+
it 'should generate hash' do
|
122
|
+
expect(Hattr::HashBuilder).to receive(:generate).with(spec, value)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
describe Hattr::HashBuilder do
|
2
|
+
subject { described_class }
|
3
|
+
|
4
|
+
TYPECAST_TEST_SET = {
|
5
|
+
Integer => { pre: '123', post: 123 },
|
6
|
+
Fixnum => { pre: '123', post: 123 },
|
7
|
+
String => { pre: 'foo', post: 'foo' },
|
8
|
+
Float => { pre: '1.2', post: 1.2 },
|
9
|
+
Symbol => { pre: 'foo', post: :foo }
|
10
|
+
}
|
11
|
+
|
12
|
+
context '::typecast' do
|
13
|
+
let(:value) { nil }
|
14
|
+
after { subject.typecast(value, type) }
|
15
|
+
|
16
|
+
context 'when type is Hash' do
|
17
|
+
let(:type) { Hash[foo: :bar] }
|
18
|
+
it 'should cast with hash_typecast' do
|
19
|
+
expect(subject).to receive(:hash_typecast).with(value, type)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when type is Array' do
|
24
|
+
let(:type) { Array[:foo] }
|
25
|
+
it 'should cast with array_typecast' do
|
26
|
+
expect(subject).to receive(:array_typecast).with(value, type)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when type is Class' do
|
31
|
+
let(:type) { Class }
|
32
|
+
it 'should cast with primitive_typecast' do
|
33
|
+
expect(subject).to receive(:primitive_typecast).with(value, type)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context '::primitive_typecast' do
|
39
|
+
context 'when class is primitive type' do
|
40
|
+
before { @result = subject.primitive_typecast(value, type) }
|
41
|
+
|
42
|
+
TYPECAST_TEST_SET.each do |key, value|
|
43
|
+
context "when type is #{key}" do
|
44
|
+
let(:value) { value[:pre] }
|
45
|
+
let(:type) { key }
|
46
|
+
|
47
|
+
it 'should cast correctly' do
|
48
|
+
expect(@result).to eql value[:post]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when class is data structure' do
|
55
|
+
let(:value) { nil }
|
56
|
+
after { subject.primitive_typecast(value, type) }
|
57
|
+
|
58
|
+
context 'when type is Array' do
|
59
|
+
let(:type) { Array }
|
60
|
+
it 'should call array_typecast' do
|
61
|
+
expect(subject).to receive(:array_typecast).with(value, described_class::ARRAY_TYPE_DEFAULT)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when type is Hash' do
|
66
|
+
let(:type) { Hash }
|
67
|
+
it 'should call hash_typecast' do
|
68
|
+
expect(subject).to receive(:hash_typecast).with(value, described_class::HASH_TYPE_DEFAULT)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context '::hash_typecast' do
|
75
|
+
let(:key_type) { String }
|
76
|
+
let(:val_type) { Integer }
|
77
|
+
let(:value) { Hash['one' => '1'] }
|
78
|
+
let(:model) { Hash[key_type => val_type] }
|
79
|
+
after { subject.hash_typecast(value.to_s, model) }
|
80
|
+
|
81
|
+
it 'should convert string to hash' do
|
82
|
+
expect(subject).to receive(:string_to_hash).with(value.to_s).and_return(value)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should convert each value to given type' do
|
86
|
+
value.values.each do |val|
|
87
|
+
expect(subject).to receive(:typecast).with(val, val_type)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when key type is Symbol' do
|
92
|
+
let(:key_type) { Symbol }
|
93
|
+
it 'should symbolize keys' do
|
94
|
+
expect(subject).to receive(:string_to_hash).with(value.to_s).and_return(value)
|
95
|
+
expect(value).to receive(:symbolize_keys).and_return(value.symbolize_keys)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context '::array_typecast' do
|
101
|
+
let(:val_type) { Integer }
|
102
|
+
let(:value) { Array['1'] }
|
103
|
+
let(:model) { Array[val_type] }
|
104
|
+
after { subject.array_typecast(value.to_s, model) }
|
105
|
+
|
106
|
+
it 'should convert string to array' do
|
107
|
+
expect(subject).to receive(:string_to_array).with(value.to_s).and_return(value)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should convert each value to given type' do
|
111
|
+
value.each do |val|
|
112
|
+
expect(subject).to receive(:typecast).with(val, val_type)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context '::string_to_hash' do
|
118
|
+
let(:value) { Hash['foo' => 'bar'] }
|
119
|
+
it 'should reverse hash.to_s' do
|
120
|
+
expect(subject.string_to_hash(value.to_s)).to eql value
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context '::string_to_array' do
|
125
|
+
let(:value) { Array['foo', 'bar'] }
|
126
|
+
it 'should reverse array.to_s' do
|
127
|
+
expect(subject.string_to_array(value.to_s)).to eql value
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
describe Hattr do
|
2
|
+
context '::extended' do
|
3
|
+
let(:receiver) { Class.new }
|
4
|
+
before { receiver.extend Hattr }
|
5
|
+
|
6
|
+
it 'should extend ClassMethods' do
|
7
|
+
expect(receiver).to respond_to :hattr
|
8
|
+
expect(receiver).to respond_to :hattr_group
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should create `hattr_groups` class variable' do
|
12
|
+
expect(receiver.hattr_groups).to be_a Hash
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hattr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Rempe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 10.5.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 10.5.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.4.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.4.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: coveralls
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: A translation for the string centric hstore extension
|
98
|
+
email:
|
99
|
+
- andrewrempe@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".coveralls.yml"
|
105
|
+
- ".editorconfig"
|
106
|
+
- ".gitignore"
|
107
|
+
- ".rspec"
|
108
|
+
- ".rubocop.yml"
|
109
|
+
- ".rubocop_todo.yml"
|
110
|
+
- ".travis.yml"
|
111
|
+
- CHANGELOG.md
|
112
|
+
- Gemfile
|
113
|
+
- LICENSE
|
114
|
+
- README.md
|
115
|
+
- Rakefile
|
116
|
+
- hattr.gemspec
|
117
|
+
- lib/hattr.rb
|
118
|
+
- lib/hattr/class_methods.rb
|
119
|
+
- lib/hattr/hash_builder.rb
|
120
|
+
- lib/hattr/version.rb
|
121
|
+
- spec/integration/hash_output_spec.rb
|
122
|
+
- spec/spec_helper.rb
|
123
|
+
- spec/unit/hattr/class_methods_spec.rb
|
124
|
+
- spec/unit/hattr/hash_builder_spec.rb
|
125
|
+
- spec/unit/hattr_spec.rb
|
126
|
+
homepage:
|
127
|
+
licenses:
|
128
|
+
- MIT
|
129
|
+
metadata: {}
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 1.9.3
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
requirements: []
|
145
|
+
rubyforge_project:
|
146
|
+
rubygems_version: 2.5.1
|
147
|
+
signing_key:
|
148
|
+
specification_version: 4
|
149
|
+
summary: PSQL hstore attributes
|
150
|
+
test_files:
|
151
|
+
- spec/integration/hash_output_spec.rb
|
152
|
+
- spec/spec_helper.rb
|
153
|
+
- spec/unit/hattr/class_methods_spec.rb
|
154
|
+
- spec/unit/hattr/hash_builder_spec.rb
|
155
|
+
- spec/unit/hattr_spec.rb
|
156
|
+
has_rdoc:
|