metahash-rb 1.0.1

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: 4017650f154dc99effbfb8b354353dc193088eed
4
+ data.tar.gz: 49824be43db47b292c7326284b2aff91257e021f
5
+ SHA512:
6
+ metadata.gz: ccdc5d25310105884c35fbbe09c07c7814ccc83953ba33e954abc3403faa862376543f9b17ecd0f3a517b9548dc5f406afcee6d9305a277fed8ceba737e5a36a
7
+ data.tar.gz: 9fa68441c4516a83050f90efc62b4369d3c5a97c959eb2b5b0b9e37f04cede5dfa014bdecc82c74e629888aead1827bdd4fd3a8238efe68961bb986d8b828c36
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ README.html
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in MetaHash.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 TinderBox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ MetaHash
2
+ ========
3
+
4
+ [![](https://ci.solanolabs.com:443/TinderBox/MetaHash/badges/110842.png?badge_token=a5097ea0ff487d291e94285ed9e4c8fabeca5fd1)](https://ci.solanolabs.com:443/TinderBox/MetaHash/suites/110842)
5
+ [![Code Climate](https://codeclimate.com/github/NullVoxPopuli/MetaHash/badges/gpa.svg)](https://codeclimate.com/github/NullVoxPopuli/MetaHash)
6
+ [![Dependency Status](https://gemnasium.com/NullVoxPopuli/MetaHash.svg)](https://gemnasium.com/NullVoxPopuli/MetaHash)
7
+
8
+
9
+ Provides a subclass of Hash and a wrapper around Rails' serialize attribute for object-like access to hashes without validating existence of nested hashes
10
+
11
+ ## Examples
12
+ #### Access nested hashes using method / object syntax
13
+
14
+ h = Metadata.new
15
+ h # => {}
16
+ h.outer.inner # => {}
17
+
18
+ #### Access to values stored in nested hashes via method call syntax
19
+
20
+ h = Metadata.new( { outer: { inner: { hash_key: "value" } } } )
21
+ h.outer.inner.hash_key # => "value"
22
+
23
+ #### Set values for nested hash structures without the nested hashes having to be initially defined
24
+
25
+ h = Metadata.new( {} )
26
+
27
+ ## Using with ActiveRecord
28
+
29
+ #### In your Gemfile
30
+
31
+ gem "metahash-rb"
32
+
33
+ #### in your ActiveRecord model
34
+
35
+ has_metadata
36
+
37
+ or
38
+
39
+ has_metadata :field_not_called_metadata
40
+
41
+
42
+ ## Support
43
+
44
+ This gem has been tested with Ruby 2.0, and rails 3.2, 4.1
45
+
46
+
47
+ ## Contributing
48
+
49
+ 1. Fork the project
50
+ 2. Create a new, descriptively named branch
51
+ 3. Add Test(s)!
52
+ 4. Commit your proposed changes
53
+ 5. Submit a pull request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,50 @@
1
+ require "metahash/metadata"
2
+ require "metahash/version"
3
+ require 'active_record'
4
+
5
+ module MetaHash
6
+
7
+ # When an active record object is loaded, convert to Metadata.
8
+ # If for whatever reason we lose this Metadata class, we can read
9
+ # the hash by creating class Metadata < Hash; end
10
+ #
11
+ # @param [Symbol] serialized_field name of the field to convert to Metadata
12
+ def has_metadata(serialized_field = "metadata")
13
+ # tell Active Record that the field is going to be JSON
14
+ # serialized, because JSON > YAML
15
+ serialize serialized_field, JSON
16
+
17
+ after_initialize do |record|
18
+ # first check the type of the field
19
+ # proceed if hash, abort if Metadata
20
+ if record.has_attribute?(serialized_field)
21
+ if [Hash, NilClass].include?(record.send(serialized_field).class)
22
+ # alias the old method / field
23
+ backup_name = "#{serialized_field}_original".to_sym
24
+ # alias_method backup_name, serialized_field
25
+ record.define_singleton_method backup_name, record.method(serialized_field)
26
+ # name the metadata accessor the same as the original field
27
+ # rails should automatically serialize this on save
28
+ initial_value = record.send(backup_name) || {}
29
+ record.send("#{serialized_field}=", Metadata.new(initial_value))
30
+ end
31
+ end
32
+ end
33
+
34
+
35
+ # create a before_save hook to store a pure Hash in the DB
36
+ before_save do |record|
37
+ @temp_metadata = record.send(serialized_field)
38
+ record.send("#{serialized_field}=", @temp_metadata.to_hash) if @temp_metadata
39
+ end
40
+
41
+ # restore the metadata to the field
42
+ after_save do |record|
43
+ record.send("#{serialized_field}=", @temp_metadata) if @temp_metadata
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ ActiveRecord::Base.send :extend, MetaHash
@@ -0,0 +1,135 @@
1
+ # MetadataHash - A specific use of ruby's Hash
2
+ #
3
+ # overrides Hash's method missing, providing the following functionality:
4
+ # 1. Access Nested hashes using the method / attribute syntax
5
+ # i.e.: h = {}
6
+ # h.middle.inner == {}
7
+ #
8
+ # 2. Access to values stored in nested hashes via method call syntax
9
+ # i.e.: h = { middle: { inner: { key: "value" } } }
10
+ # h.middle.inner.key == "value"
11
+ #
12
+ # 3. Set values for nested hash structures without middle nested hashes
13
+ # having to be defined
14
+ # i.e.: h = {}
15
+ # h.middle.inner = 3
16
+ # h == { middle: { inner: 3 } }
17
+ #
18
+ # 4. Old hash square bracket access still works
19
+ # i.e.: h = { inner: { key: "value" } }
20
+ # h[:inner][:key] == "value"
21
+ #
22
+ class Metadata < Hash
23
+
24
+ # the hash being passed in will have all its subhashes converted to
25
+ # metadata hashes.
26
+ # this is needed to we can have the
27
+ #
28
+ # @raise [ArgumentError] if one of the keys is method of Hash
29
+ # @raise [ArgumentError] if hash is not a type of Hash or Metadata
30
+ # @param [Hash] hash the structure to convert to Metadata
31
+ def initialize(hash = {})
32
+ # for maybe instantiating nested hashes that we
33
+ # aren't yet sure if they are going to have values or not
34
+ @empty_nested_hashes = []
35
+
36
+ if hash.is_a?(Metadata)
37
+ # we have nothing to do
38
+ return hash
39
+ elsif hash.is_a?(Hash)
40
+ # recursively create nested metadata objects
41
+ hash.each do |key, value|
42
+ if not valid_key?(key)
43
+ raise ArgumentError.new("Not Allowed. '#{key}' is a reserved method.")
44
+ end
45
+
46
+ self[key] = (
47
+ if value.is_a?(Hash)
48
+ Metadata.new(value)
49
+ elsif value.is_a?(Array)
50
+ # ensure hashes kept in an array are also converted to metadata
51
+ array = value.map{ |element|
52
+ element.is_a?(Hash) ? Metadata.new(element) : element
53
+ }
54
+ else
55
+ value
56
+ end
57
+ )
58
+ end
59
+ else
60
+ raise ArgumentError.new("Field must be a Hash or Metadata")
61
+ end
62
+ end
63
+
64
+
65
+ # this is what allows functionality mentioned in the class comment to happen
66
+ # @raise [ArgumentError] if one of the keys is method of Hash
67
+ def method_missing(method_name, *args)
68
+ # check for assignment
69
+ if (key = method_name.to_s).include?("=")
70
+ key = key.chop.to_sym
71
+ if not self.valid_key?(key)
72
+ raise ArgumentError.new("Not Allowed. '#{key}' is a reserved method.")
73
+ end
74
+
75
+ if not @empty_nested_hashes.empty?
76
+ deepest_metadata = self
77
+ @empty_nested_hashes.each do |key|
78
+ deepest_metadata = deepest_metadata[key] = Metadata.new
79
+ end
80
+ @empty_nested_hashes = []
81
+ deepest_metadata[key] = args[0]
82
+ end
83
+ else
84
+ value = self[method_name]
85
+ if not value
86
+ @empty_nested_hashes << method_name.to_sym
87
+ value = self
88
+ end
89
+ value
90
+ end
91
+
92
+ end
93
+
94
+ # Metdata has indifferent access
95
+ def [](key)
96
+ super(key.to_sym)
97
+ end
98
+
99
+ # # Metadata has indifferent access,
100
+ # # so just say that all the keys are symbols.
101
+ def []=(key, value)
102
+ super(key.to_sym, value)
103
+ end
104
+
105
+ # tests the ability to use this key as a key in a hash
106
+ # @param [Symbol] key
107
+ # @return [Boolean] whether or not this can be used as a hash key
108
+ def valid_key?(key)
109
+ # second parameter says that we are
110
+ # looking at private methods as well
111
+ not self.respond_to?(key, true)
112
+ end
113
+
114
+ # convert to regular hash, recursively
115
+ def to_hash
116
+ hash = {}
117
+ self.each do |k,v|
118
+ hash[k] = (
119
+ if v.is_a?(Metadata)
120
+ v.to_hash
121
+ elsif v.is_a?(Array)
122
+ v.map{ |e| e.is_a?(Metadata) ? e.to_hash : e }
123
+ else
124
+ v
125
+ end
126
+ )
127
+ end
128
+
129
+ hash
130
+ end
131
+
132
+ def to_ary
133
+ self.to_hash.to_a
134
+ end
135
+ end
@@ -0,0 +1,3 @@
1
+ module MetaHash
2
+ VERSION = "1.0.1"
3
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "metahash/version"
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "metahash-rb"
9
+ s.version = MetaHash::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.license = "MIT"
12
+ s.authors = ["L. Preston Sego III"]
13
+ s.email = "LPSego3+dev@gmail.com"
14
+ s.homepage = "https://github.com/NullVoxPopuli/MetaHash"
15
+ s.summary = "MetaHash-#{MetaHash::VERSION}"
16
+ s.description = "Provides a subclass of Hash and a wrapper around Rails' serialize attribute for object-like access to hashes without validating existence of nested hashes."
17
+
18
+
19
+ s.files = `git ls-files`.split($/)
20
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
21
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
22
+ s.require_paths = ["lib"]
23
+
24
+
25
+ s.add_runtime_dependency "activerecord", ">= 3.0.0"
26
+ s.add_development_dependency "bundler"
27
+ s.add_development_dependency "rspec"
28
+ s.add_development_dependency "sqlite3"
29
+ s.add_development_dependency "pry-byebug"
30
+ s.add_development_dependency "codeclimate-test-reporter"
31
+
32
+ end
@@ -0,0 +1,58 @@
1
+ require "spec_helper"
2
+
3
+ describe Metadata do
4
+
5
+ it "is a hash" do
6
+ expect(Metadata.new).to be_kind_of(Hash)
7
+ end
8
+
9
+ describe "pruning empty hashes" do
10
+ let(:m){Metadata.new}
11
+
12
+ it "removes empty hashes" do
13
+ m.a.b.c
14
+ expect(m.send :prune).to eq Metadata.new
15
+ end
16
+
17
+ it "does not remove valid hashes" do
18
+ m.a.b = 2
19
+ expect(m.send :prune).to_not eq Metadata.new
20
+ end
21
+
22
+ it "does not pollute itself with empty hashes" do
23
+ m.a.b.c
24
+ expect(m).to eq Metadata.new
25
+ end
26
+ end
27
+
28
+
29
+ it "sets a non-exsiting deep value" do
30
+ m = Metadata.new
31
+ m.a.b = 2
32
+ expect(m.a.b).to eq 2
33
+ end
34
+
35
+ it "has indifferent access" do
36
+ m = Metadata.new(a: 2)
37
+ expect(m.a).to eq 2
38
+ expect(m[:a]).to eq 2
39
+ expect(m["a"]).to eq 2
40
+ end
41
+
42
+ it "allows method-style access to nested hashes" do
43
+ m = Metadata.new(h)
44
+ expect(m.outer.inner.k).to eq h[:outer][:inner][:k]
45
+ end
46
+
47
+ it "allows hash-style access to nested hashes" do
48
+ m = Metadata.new(h)
49
+ expect(m[:outer][:inner][:k]).to eq h[:outer][:inner][:k]
50
+ end
51
+
52
+ it "does not allow hash keys to conflict with the name of a method of the Hash object" do
53
+ expect{
54
+ Metadata.new( outer: { key: "value" } )
55
+ }.to raise_error
56
+ end
57
+
58
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe MetaHash do
4
+
5
+
6
+ before(:each) do
7
+ m = TestObject.new(metadata: h, name: "MetaHash")
8
+ m.save
9
+ end
10
+
11
+ after(:each) do
12
+ TestObject.destroy_all
13
+ end
14
+
15
+ it "converts the chose field to Metadata" do
16
+ p = TestObject.last
17
+ expect(p.metadata).to be_kind_of(Metadata)
18
+ end
19
+
20
+ it "instantiates with an empty hash if the field is nil" do
21
+ p = TestObject.new
22
+ p.metadata = nil
23
+ p = TestObject.last
24
+ expect(p.metadata).to_not be_nil
25
+ expect(p.metadata).to be_kind_of(Metadata)
26
+ end
27
+
28
+ it "does not modify the original contents of the chosen field on initialization" do
29
+ p = TestObject.last
30
+ expect(p.metadata.to_hash).to eq Metadata.new(h)
31
+ end
32
+
33
+ it "creates an alias for the original field's data" do
34
+ p = TestObject.last
35
+ expect(p.metadata_original).to eq Metadata.new(h)
36
+ end
37
+
38
+ it "doesn't error when a the attribute is not included in the retrieval of a record" do
39
+ expect{
40
+ p = TestObject.select("name")
41
+ }.to_not raise_error
42
+ end
43
+
44
+ it "converts the field back to a Hash before saving to the database" do
45
+ db = ActiveRecord::Base.connection.select_all("
46
+ SELECT metadata
47
+ FROM test_objects
48
+ ").last
49
+ p = db["metadata"]
50
+ expect(JSON.load(p)).to eq JSON.load(h.to_json)
51
+ end
52
+
53
+
54
+ end
@@ -0,0 +1,32 @@
1
+
2
+ require "rubygems"
3
+ require "bundler/setup"
4
+ require "metahash"
5
+ require "pry-byebug" # binding.pry to debug!
6
+ require "codeclimate-test-reporter"
7
+ CodeClimate::TestReporter.start
8
+
9
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
10
+ load File.dirname(__FILE__) + '/support/schema.rb'
11
+ Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each {|file| require file }
12
+
13
+
14
+
15
+
16
+ # This file was generated by the `rspec --init` command. Conventionally, all
17
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
18
+ # Require this file using `require "spec_helper"` to ensure that it is only
19
+ # loaded once.
20
+ #
21
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
22
+ RSpec.configure do |config|
23
+ config.treat_symbols_as_metadata_keys_with_true_values = true
24
+ config.run_all_when_everything_filtered = true
25
+ config.filter_run :focus
26
+
27
+ # Run specs in random order to surface order dependencies. If you find an
28
+ # order dependency and want to debug it, you can fix the order by providing
29
+ # the seed, which is printed after each run.
30
+ # --seed 1234
31
+ config.order = 'random'
32
+ end
@@ -0,0 +1,3 @@
1
+ class TestObject < ActiveRecord::Base
2
+ has_metadata
3
+ end
@@ -0,0 +1,9 @@
1
+ def h
2
+ {
3
+ outer: {
4
+ inner: {
5
+ k: "value"
6
+ }
7
+ }
8
+ }
9
+ end
@@ -0,0 +1,8 @@
1
+ ActiveRecord::Schema.define do
2
+ self.verbose = false
3
+
4
+ create_table :test_objects, :force => true do |t|
5
+ t.string :name
6
+ t.text :metadata
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ :tddium:
2
+ :ruby_version: ruby-2.1.2
3
+ :coverage: true
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: metahash-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - L. Preston Sego III
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
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: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
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: sqlite3
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: pry-byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: codeclimate-test-reporter
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: Provides a subclass of Hash and a wrapper around Rails' serialize attribute
98
+ for object-like access to hashes without validating existence of nested hashes.
99
+ email: LPSego3+dev@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - Gemfile
107
+ - LICENSE
108
+ - README.md
109
+ - Rakefile
110
+ - lib/metahash.rb
111
+ - lib/metahash/metadata.rb
112
+ - lib/metahash/version.rb
113
+ - metahash.gemspec
114
+ - spec/metadata_spec.rb
115
+ - spec/metahash_spec.rb
116
+ - spec/spec_helper.rb
117
+ - spec/support/models/test_object.rb
118
+ - spec/support/sample_hashes.rb
119
+ - spec/support/schema.rb
120
+ - tddium.yml
121
+ homepage: https://github.com/NullVoxPopuli/MetaHash
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.4.1
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: MetaHash-1.0.1
145
+ test_files:
146
+ - spec/metadata_spec.rb
147
+ - spec/metahash_spec.rb
148
+ - spec/spec_helper.rb
149
+ - spec/support/models/test_object.rb
150
+ - spec/support/sample_hashes.rb
151
+ - spec/support/schema.rb