hattr 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/arempe93/hattr.svg?branch=master)](https://travis-ci.org/arempe93/hattr)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/hattr.svg)](https://rubygems.org/gems/hattr)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/arempe93/hattr/badge.svg?branch=master&service=github)](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:
|