hattr 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1 @@
1
+ service_name: travis-ci
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 2
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.{sh,markdown}]edito
12
+ indent_size = 4
@@ -0,0 +1,3 @@
1
+ coverage
2
+ .DS_Store
3
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format=documentation
@@ -0,0 +1,10 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+ DisplayCopNames: true
4
+ Include:
5
+ - 'lib/**/*'
6
+ Exclude:
7
+ - 'spec/**/*'
8
+ - 'vendor/**/*'
9
+
10
+ inherit_from: .rubocop_todo.yml
@@ -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
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ sudo: false
3
+
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.1.8
7
+ - 2.2.4
8
+ - 2.3.0
9
+
10
+ branches:
11
+ only:
12
+ - master
@@ -0,0 +1,7 @@
1
+ ### Next release
2
+
3
+ Add your contribution here
4
+
5
+ ### v1.0.0
6
+
7
+ First stable release!
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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.
@@ -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
@@ -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
@@ -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
@@ -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,4 @@
1
+ # frozen_string_literal: true
2
+ module Hattr
3
+ VERSION = '1.0.0'
4
+ 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
@@ -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: