pobject 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 20dc0805c434c64cf463c8d9b466592c07c05f08
4
+ data.tar.gz: ee6b8c53b78befa5a1ef90e3e74b0a330fecced3
5
+ SHA512:
6
+ metadata.gz: d15a6cbca9c7fdb0af9b99433cea357d1c4c78e696736ed41b5e8d15d260660a2e80a430c83cada6a4f35c4160b7634e2e9e47e69e68a502beb428cb5dd904dd
7
+ data.tar.gz: 8cd94ca4e4692ddb785c1eaa66821e9ec9eb9462e833d49238bea4ea901aaa037a01d7797c8dabe2bbee29b7fd3b359e703f7ed8ff41e353b692f7c8ed34f7fb
data/README.md ADDED
@@ -0,0 +1,128 @@
1
+ Persistent Object
2
+ ==================================================
3
+
4
+ [![Gem](https://img.shields.io/gem/v/pobject.svg?style=flat-square)](https://rubygems.org/gems/pobject)
5
+ [![Travis](https://img.shields.io/travis/DannyBen/pobject.svg?style=flat-square)](https://travis-ci.org/DannyBen/pobject)
6
+ [![Code Climate](https://img.shields.io/codeclimate/github/DannyBen/pobject.svg?style=flat-square)](https://codeclimate.com/github/DannyBen/pobject)
7
+ [![Gemnasium](https://img.shields.io/gemnasium/DannyBen/pobject.svg?style=flat-square)](https://gemnasium.com/DannyBen/pobject)
8
+
9
+ ---
10
+
11
+ Transparently persist objects to disk as YAML or PStore files.
12
+
13
+ ---
14
+
15
+ Install
16
+ --------------------------------------------------
17
+
18
+ ```
19
+ $ gem install pobject
20
+ ```
21
+
22
+ Or with bundler:
23
+
24
+ ```ruby
25
+ gem 'pobject'
26
+ ```
27
+
28
+ Usage
29
+ --------------------------------------------------
30
+
31
+ Your object should inherit from PObject.
32
+
33
+ ```ruby
34
+ require 'pobject'
35
+
36
+ class Settings < PObject
37
+ end
38
+ ```
39
+
40
+ Now, any time you access a property, it is saved to a file. By default, we will
41
+ svae a YAML file with the same name as the class:
42
+
43
+
44
+ ```ruby
45
+ class Settings < PObject
46
+ end
47
+
48
+ config = Settings.new
49
+ config.port = 3000
50
+ # Will create a 'settings.yml' file and store the port value
51
+ ```
52
+
53
+ You can access any attributes by either dot notation or hash notation.
54
+
55
+ ```
56
+ config.port
57
+ # => 3000
58
+
59
+ config[:port]
60
+ # => 3000
61
+ ```
62
+
63
+ To change the location of the file, simply override the `to_store` method.
64
+
65
+ ```ruby
66
+ class Settings < PObject
67
+ def to_store
68
+ "config/local.yml"
69
+ end
70
+ end
71
+
72
+ config = Settings.new
73
+ config.port = 3000
74
+ # Will create a 'config/local.yml'
75
+ ```
76
+
77
+ Whenever you use the `.yml` extension, PObject will store a YAML file. If you
78
+ wish to store a `PStore` object instead, provide any other extension:
79
+
80
+ ```ruby
81
+ class Settings < PObject
82
+ def to_store
83
+ "config/local.pstore"
84
+ end
85
+ end
86
+ ```
87
+
88
+ To store several objects in one store file, your `to_store` method should
89
+ return an array with two elements: The first, is the path to the file and
90
+ the second is any unique key identifying the instance.
91
+
92
+ ```ruby
93
+ class Hero < PObject
94
+ def initialize(id)
95
+ @id = id
96
+ end
97
+
98
+ def to_store
99
+ ["heroes.yml", @id]
100
+ end
101
+ end
102
+
103
+ hammer = Hero.new :hammer
104
+ raynor = Hero.new :raynor
105
+
106
+ hammer.name = 'Sgt. Hammer'
107
+ raynor.name = 'Raynor'
108
+ # =>
109
+ # ---
110
+ # :hammer:
111
+ # :name: Sgt. Hammer
112
+ # :raynor:
113
+ # :name: Raynor
114
+ ```
115
+
116
+ By default, PObject will raise an error when accessing a property that does
117
+ not exist. To change this behavior, call `allow_missing` at the beinning of
118
+ your object
119
+
120
+ ```ruby
121
+ class Book < PObject
122
+ allow_missing
123
+ end
124
+
125
+ book = Book.new
126
+ book.author
127
+ # => nil
128
+ ```
@@ -0,0 +1,156 @@
1
+ require 'pstore'
2
+ require 'yaml/store'
3
+
4
+ class PObject
5
+ # When `allow_missing` is called at the inheriting class level, then
6
+ # all calls to any missing attribute will return `nil` and supress the
7
+ # normal ruby behavior of raising a `NoMethodError`.
8
+ def self.allow_missing
9
+ send :define_method, :allow_missing? do
10
+ true
11
+ end
12
+ end
13
+
14
+ # Intercept any call to an unknown method, and try to load it from the
15
+ # store. If it does not exist in the store as well, then raise the usual
16
+ # error.
17
+ # This method will also be called when trying to assign to a non existing
18
+ # attribute. In this case, we will save it to the store.
19
+ def method_missing(method, args=nil, &_block)
20
+ if method.to_s[-1] == "="
21
+ set method.to_s.chomp('=').to_sym, args
22
+ else
23
+ content = get method
24
+ content ? content : allow_missing? ? nil : super
25
+ end
26
+ end
27
+
28
+ # Mirror the method_missing behavior.
29
+ def respond_to_missing?(method, include_private=false)
30
+ if method.to_s[-1] == "="
31
+ true
32
+ else
33
+ allow_missing? or !!get(method.to_sym)
34
+ end
35
+ end
36
+
37
+ # Allow hash-style access to any of our stored properties.
38
+ # This will not reload from the disk more than once.
39
+ def [](key)
40
+ get key
41
+ end
42
+
43
+ # Allow hash-style assignment to new peoperties
44
+ def []=(key, value)
45
+ set key, value
46
+ end
47
+
48
+ # Return a nice string when inspecting or printing.
49
+ # This will load values from the store if they were not already loaded.
50
+ def inspect
51
+ properties = attributes.map { |var| [var, get(var)].join(":") }.join ', '
52
+ properties = " #{properties}" unless properties.empty?
53
+ "<#{self.class.name}#{properties}>"
54
+ end
55
+ alias_method :to_s, :inspect
56
+
57
+ # Return attribute names, so we can print them and their values in
58
+ # `inspect`.
59
+ def attributes
60
+ @attributes ||= attributes!
61
+ end
62
+
63
+ # Return attribute names from the store.
64
+ # TODO: Maybe also merge with instance_variables. Pay attention to @s.
65
+ def attributes!
66
+ store.transaction { store.roots }.sort
67
+ end
68
+
69
+ private
70
+
71
+ # We need to have a default `allow_missing?` method, which may be
72
+ # overridden by calling `allow_missing`.
73
+ def allow_missing?
74
+ false
75
+ end
76
+
77
+ # Get a value from the store, and set it as an instance variable so that
78
+ # subsequent calls do not access the disk.
79
+ def get(key)
80
+ result = instance_variable_get("@#{key}")
81
+ result.nil? ? get!(key) : result
82
+ end
83
+
84
+ # Hard get value from the store.
85
+ def get!(key)
86
+ result = store.transaction do
87
+ if store_key
88
+ store[store_key] ||= {}
89
+ store[store_key][key]
90
+ else
91
+ store[key]
92
+ end
93
+ end
94
+ instance_variable_set "@#{key}", result
95
+ end
96
+
97
+ # Set a key=value pair in the store, and in an instance variable so that
98
+ # any subsequent call to `get` will not have to read it from the disk.
99
+ # In addition, add it to the attributes list, which is used by `inspect`.
100
+ def set(key, value)
101
+ instance_variable_set "@#{key}", value
102
+ attributes << key unless attributes.include? key
103
+ store.transaction do
104
+ if store_key
105
+ store[store_key] ||= {}
106
+ store[store_key][key] = value
107
+ else
108
+ store[key] = value
109
+ end
110
+ end
111
+ end
112
+
113
+ # The default store location. May be overridden by the inheriting class.
114
+ def to_store
115
+ "#{self.class.to_s.downcase}.yml"
116
+ end
117
+
118
+ # Return the actual store object. It can either be a YAML::Store or a
119
+ # PStore, which behave exactly the same.
120
+ def store
121
+ @store ||= store!
122
+ end
123
+
124
+ # Hard get the actual store object, based on the provided file extension.
125
+ def store!
126
+ if ['.yml', 'yaml'].include?(store_file[-4,4])
127
+ YAML::Store.new(store_file)
128
+ else
129
+ PStore.new(store_file)
130
+ end
131
+ end
132
+
133
+ # Return the store filename.
134
+ def store_file
135
+ @store_file ||= store_file!
136
+ end
137
+
138
+ # Hard return the store filename based on the value of `to_store`.
139
+ # If an array, the first element is the path.
140
+ def store_file!
141
+ to_store.is_a?(Array) ? to_store[0] : to_store
142
+ end
143
+
144
+ # Return the key to use in case multiple objects are to be saved in the
145
+ # same file.
146
+ def store_key
147
+ @store_key.nil? ? @store_key = store_key! : @store_key
148
+ end
149
+
150
+ # Hard return the store key based on the `to_store` value. If an array,
151
+ # the second element represents the key.
152
+ def store_key!
153
+ to_store.is_a?(Array) ? to_store[1] : false
154
+ end
155
+
156
+ end
@@ -0,0 +1,3 @@
1
+ class PObject
2
+ VERSION = "0.1.0"
3
+ end
data/lib/pobject.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'pobject/version'
2
+ require 'pobject/pobject'
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pobject
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Danny Ben Shitrit
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: runfile
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: runfile-tasks
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.4'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '9.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '9.0'
83
+ description: Auto save objects as files
84
+ email: db@dannyben.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - README.md
90
+ - lib/pobject.rb
91
+ - lib/pobject/pobject.rb
92
+ - lib/pobject/version.rb
93
+ homepage: https://github.com/DannyBen/pobject
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: 2.0.0
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.5.1
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: Persistent Object
117
+ test_files: []