skn_utils 1.4.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.
@@ -0,0 +1,19 @@
1
+ ##
2
+ # <Rails.root>/lib/skn_utils/generic_bean.rb
3
+ #
4
+ # *** See SknUtils::NestedResultBase for details ***
5
+ #
6
+ ##
7
+ # (Defaults)
8
+ # :enable_serialization = true
9
+ # :depth = :multi
10
+
11
+ module SknUtils
12
+
13
+ class GenericBean < NestedResultBase
14
+ def initialize(params={})
15
+ super( params.merge({enable_serialization: true}) )
16
+ end
17
+ end # end class
18
+
19
+ end # end module
@@ -0,0 +1,233 @@
1
+ ##
2
+ # <Rails.root>/lib/skn_utils/nested_result_base.rb
3
+ #
4
+ # Creates an Object with instance variables and associated getters and setters for hash each input key.
5
+ # If the key's value is also a hash itself, it too will become an Object.
6
+ # if the key's value is a Array of Hashes, each element of the Array will become an Object.
7
+ #
8
+ # This nesting action is controlled by the value of the option key ':depth'.
9
+ # The key :depth defaults to :multi, an has options of :single, or :multi_with_arrays
10
+ #
11
+ # The ability of the resulting Object to be Marshalled(dump/load) can be preserved by merging
12
+ # into the input params key ':enable_serialization' set to true. It defaults to false for speed purposes
13
+ #
14
+ ##
15
+ # Operational Options
16
+ # --------------------------------
17
+ # :enable_serialization = false -- [ true | false ], for speed, omits creation of attr_accessor
18
+ # :depth = :multi -- [ :single | :multi | :multi_with_arrays ]
19
+ ##
20
+ # Public Components
21
+ # --------------------------------
22
+ # Inherit from NestedResultBase
23
+ # or Include AttributeHelpers
24
+ ##
25
+ #
26
+ # Basic function includes:
27
+ # - provides the hash or dot notation methods of accessing values from object created; i.e
28
+ # 'obj = ResultBean.new({value1: "some value", value2: {one: 1, two: "two"}})
29
+ # 'x = obj.value1' or 'x = obj.value2.one'
30
+ # 'x = obj["value1"]'
31
+ # 'x = obj[:value1]'
32
+ #
33
+ # - enables serialization by avoiding the use of 'singleton_class' methods which breaks Serializers
34
+ # Serializer supports xml, json, hash, and standard Marshall'ing
35
+ #
36
+ # person = PageControls.new({name: "Bob"})
37
+ # person.attributes # => {"name"=>"Bob"}
38
+ # person.serializable_hash # => {"name"=>"Bob"}
39
+ # person.to_hash # => {"name"=>"Bob"}
40
+ # person.to_json # => "{\"name\":\"Bob\"}"
41
+ # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<page-controls>\n <name>Bob</name>\n</page-controls>\n"
42
+ # dmp = Marshal.dump(person) # => "\x04\bo:\x1ASknUtils::PageControls\x06:\n@nameI\"\bBob\x06:\x06ET"
43
+ # person = Marshal.load(dmp) # => #<SknUtils::PageControls:0x007faede906d40 @name="Bob">
44
+ #
45
+ # ***GenericBean designed to automatically handle the setup for serialization and multi level without arrays
46
+ #
47
+ # - post create additions:
48
+ # 'obj = ResultBean.new({value1: "some value", value2: {one: 1, two: "two"}})
49
+ # 'x = obj.one' --causes NoMethodError
50
+ # 'x = obj.one = 'some other value' --creates a new instance value with accessors
51
+ # 'x = obj.one = {key1: 1, two: "two"}' --creates a new ***bean as the value of obj.one
52
+ # 'y = obj.one.two' --returns "two"
53
+ # 'y = obj.one[:two] --returns "two"
54
+ # 'y = obj.one['two'] --returns "two"
55
+ #
56
+ # - supports predicates <attr>? and clear_<attr>? method patterns
57
+ # 'obj = PageControls.new({name: "Something", phone: "2604815365"})'
58
+ # 'obj.name?' # => true true or false, like obj.name.present?
59
+ # 'obj.clear_name' # => nil sets :name to nil
60
+ #
61
+ ##
62
+ # The combination of this NestedResultBase(dot notation class) and AttributeHelpers(hash notation module), produces
63
+ # this effect from an input hash:
64
+ #
65
+ # {:depth => <select>, ...} Input Hash Basic dot notation
66
+ # ------------------------- --------------------------------------- ---------------------------------
67
+ #
68
+ ## (DOES NOT FOLLOW Values)
69
+ # * :single - {one: 1, drb.one = 1
70
+ # two: { one: 1, drb.two = {one: 1, two: 'two}
71
+ # two: "two" drb.two.two = NoMethodError
72
+ # },
73
+ # three: [ {one: 'one', two: 2}, drb.three = [{one: 'one', two: 2},{three: 'three', four: 4}]
74
+ # {three: 'three', four: 4} drb.three[1] = {three: 'three', four: 4}
75
+ # ] drb.three[1].four = NoMethodError
76
+ # }
77
+ #
78
+ ## (Follow VALUES that are Hashes only.)
79
+ # * :multi - {one: 1, drb.one = 1
80
+ # two: { one: 1, drb.two.one = 1
81
+ # two: "two" drb.two.two = 'two'
82
+ # },
83
+ # three: [ {one: 'one', two: 2}, drb.three = [{one: 'one', two: 2},{three: 'three', four: 4}]
84
+ # {three: 'three', four: 4} drb.three[1] = {three: 'three', four: 4}
85
+ # ] drb.three[1].four = NoMethodError
86
+ # }
87
+ #
88
+ ## (Follow VALUES that are Hashes and/or Arrays of Hashes)
89
+ # * :multi_with_arrays - {one: 1, drb.one = 1
90
+ # two: { one: 1, drb.two.one = 1
91
+ # two: "two" drb.two.two = 'two'
92
+ # },
93
+ # three: [ {one: 'one', two: 2}, drb.three.first.one = 'one'
94
+ # {three: 'three', four: 4} drb.three[1].four = 4
95
+ # ]
96
+ # }
97
+ #
98
+ ##
99
+ # -- SubClassing Usage Examples --
100
+ #
101
+ # (DOES NOT FOLLOW Values)
102
+ # class SmallPackage < NestedResultBase
103
+ # def initialize(params={})
104
+ # super( params.merge({depth: :single}) ) # override default of :multi level
105
+ # end
106
+ # end
107
+ #
108
+ # (Follow VALUES that are Hashes only.)
109
+ # class ResultBean < NestedResultBase
110
+ # # defaults to :multi level
111
+ # end
112
+ # -- or --
113
+ # class ResultBean < NestedResultBase
114
+ # def initialize(params={})
115
+ # # your other init stuff here
116
+ # super(params) # default taken
117
+ # end
118
+ # end
119
+ # -- or --
120
+ # class ResultBean < NestedResultBase
121
+ # def initialize(params={})
122
+ # # your other init stuff here
123
+ # super( params.merge({depth: :multi}) ) # Specified
124
+ # end
125
+ # end
126
+ # ** - or -- enable serialization and default to multi
127
+ # class GenericBean < NestedResultBase
128
+ # def initialize(params={})
129
+ # super( params.merge({enable_serialization: true}) ) # Specified with Serialization Enabled
130
+ # end
131
+ # end
132
+ #
133
+ # (Follow VALUES that are Hashes and/or Arrays of Hashes, and enable Serializers)
134
+ # class PageControl < NestedResultBase
135
+ # def initialize(params={})
136
+ # super( params.merge({depth: :multi_with_arrays, enable_serialization: true}) ) # override defaults
137
+ # end
138
+ # end
139
+ #
140
+ ##
141
+ # NOTE: Cannot be Marshalled/Serialized unless input params.merge({enable_serialization: true}) -- default is false
142
+ # Use GenericBean if serialization is needed, it sets this value to true automatically
143
+ ##
144
+
145
+
146
+ module SknUtils
147
+
148
+ class NestedResultBase
149
+ include ActiveModel::Serialization
150
+ include ActiveModel::Serializers::JSON
151
+ include ActiveModel::Serializers::Xml
152
+ include AttributeHelpers
153
+
154
+ # :depth controls how deep into input hash/arrays we convert
155
+ # :depth => :single | :multi | :multi_with_arrays
156
+ # :depth defaults to :multi
157
+ # :enable_serialization controls the use of singleton_method() to preserve the ability to Marshal
158
+ # :enable_serialization defaults to false
159
+ def initialize(params={})
160
+ @skn_enabled_depth = params.delete(:depth) {|not_found| :multi }
161
+ @skn_enable_serialization = params.delete(:enable_serialization) {|not_found| false }
162
+ case depth_level
163
+ when :single
164
+ single_level_initializer(params)
165
+ when :multi_with_arrays
166
+ multi_level_incl_arrays_initializer(params)
167
+ else
168
+ multi_level_initializer(params)
169
+ end
170
+ end
171
+
172
+ def single_level_initializer(params={}) # Single Level Initializer -- ignore value eql hash
173
+ params.each do |k,v|
174
+ key = clean_key(k)
175
+ singleton_class.send(:attr_accessor, key) unless respond_to?(key) or serialization_required?
176
+ instance_variable_set("@#{key}".to_sym,v)
177
+ end
178
+ end
179
+
180
+ def multi_level_initializer(params={}) # Multi Level Initializer -- value eql hash then interate
181
+ params.each do |k,v|
182
+ key = clean_key(k)
183
+ singleton_class.send(:attr_accessor, key) unless respond_to?(key) or serialization_required?
184
+ if v.kind_of?(Hash)
185
+ instance_variable_set("@#{key}".to_sym, self.class.new(v))
186
+ else
187
+ instance_variable_set("@#{key}".to_sym,v)
188
+ end
189
+ end
190
+ end
191
+
192
+ def multi_level_incl_arrays_initializer(params={}) # Multi Level Initializer including Arrays of Hashes
193
+ params.each do |k,v|
194
+ key = clean_key(k)
195
+ singleton_class.send(:attr_accessor, key) unless respond_to?(key) or serialization_required?
196
+ if v.kind_of?(Array) and v.first.kind_of?(Hash)
197
+ instance_variable_set("@#{key}".to_sym, (v.map {|nobj| self.class.new(nobj)}) )
198
+ elsif v.kind_of?(Hash)
199
+ instance_variable_set("@#{key}".to_sym, self.class.new(v))
200
+ else
201
+ instance_variable_set("@#{key}".to_sym,v)
202
+ end
203
+ end
204
+ end
205
+
206
+ # enablement for latter additions
207
+ def serialization_required?
208
+ @skn_enable_serialization
209
+ end
210
+
211
+ # enablement for latter additions
212
+ def depth_level
213
+ @skn_enabled_depth
214
+ end
215
+
216
+ # Some keys have chars not suitable for symbol keys
217
+ def clean_key(original)
218
+ formatted_key = original.to_s
219
+ if /^[#|@|:]/.match(formatted_key) # filter out (@xsi) from '@xsi:type' keys
220
+ label = /@(.+):(.+)/.match(formatted_key) || /[#|@|:](.+)/.match(formatted_key) || []
221
+ formatted_key = case label.size
222
+ when 1
223
+ label[1].to_s
224
+ when 2
225
+ "#{label[1]}_#{label[2]}"
226
+ else
227
+ original # who knows what it was, give it back
228
+ end
229
+ end
230
+ formatted_key
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,20 @@
1
+ ##
2
+ # <Rails.root>/lib/skn_utils/page_controls.rb
3
+ #
4
+ # *** See SknUtils::NestedResultBase for details ***
5
+ #
6
+ ##
7
+ # (Defaults)
8
+ # :enable_serialization = true -- for function
9
+ # :depth = :multi_with_arrays
10
+ ##
11
+
12
+ module SknUtils
13
+
14
+ class PageControls < NestedResultBase
15
+ def initialize(params={})
16
+ super( params.merge({enable_serialization: true, depth: :multi_with_arrays}) )
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,17 @@
1
+ ##
2
+ # <Rails.root>/lib/skn_utils/result_bean.rb
3
+ #
4
+ # *** See SknUtils::NestedResultBase for details ***
5
+ #
6
+ ##
7
+ # (Defaults)
8
+ # :enable_serialization = false -- for speed
9
+ # :depth = :multi
10
+ ##
11
+
12
+ module SknUtils
13
+
14
+ class ResultBean < NestedResultBase
15
+ end
16
+
17
+ end
@@ -0,0 +1,43 @@
1
+ ##
2
+ # <Rails.root>/lib/skn_utils/result_bean_with_errors.rb
3
+ #
4
+ # *** See SknUtils::NestedResultBase for details ***
5
+ #
6
+ ##
7
+ # (Defaults)
8
+ # :enable_serialization = false -- for speed
9
+ # :depth = :multi
10
+ ##
11
+ # Add the ActiveModel::Errors Object to bean structure, and
12
+ # filters @errors out of serialization features; i.e. not included in *.attributes()
13
+ #
14
+ # bean.errors.add(:name, "can not be nil") if name == nil
15
+ ###
16
+
17
+ module SknUtils
18
+
19
+ class ResultBeanWithErrors < NestedResultBase
20
+ include ActiveModel::Conversion
21
+ extend ActiveModel::Naming
22
+
23
+ attr_reader :errors
24
+
25
+ def initialize(params={})
26
+ @errors = params.delete :errors
27
+ @errors = ActiveModel::Errors.new(self) unless @errors.present?
28
+ super(params)
29
+ end
30
+
31
+ def read_attribute_for_validation(attr)
32
+ send(attr)
33
+ end
34
+
35
+ def self.human_attribute_name(attr, options = {})
36
+ attr
37
+ end
38
+
39
+ def self.lookup_ancestors
40
+ [self]
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module SknUtils
2
+ VERSION = "1.4.0"
3
+ end
data/skn_utils.gemspec ADDED
@@ -0,0 +1,50 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'skn_utils/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'skn_utils'
8
+ spec.version = SknUtils::VERSION
9
+ spec.author = 'James Scott Jr'
10
+ spec.email = 'skoona@gmail.com'
11
+ spec.summary = <<EOF
12
+ Ruby convenience utilities, the first being a ResultBean.
13
+
14
+ ResultBean is a PORO (Plain Old Ruby Object) which inherits from NestedResultBean class (inlcuded). This class
15
+ is intantiated via a hash at Ruby/Rails Runtime, allows access to vars via dot or hash notation,
16
+ and is serializable via to_xml, to_hash, and to_json.
17
+ EOF
18
+
19
+ spec.description = <<EOF
20
+ Creates an PORO Object with instance variables and associated getters and setters for each input key, during runtime.
21
+
22
+ If a key's value is also a hash, it too can optionally become an Object.
23
+
24
+ If a key's value is a Array of Hashes, each element of the Array can optionally become an Object.
25
+
26
+ This nesting action is controlled by the value of the options key ':depth'. Options key :depth defaults
27
+ to :multi, and has options of :single, :multi, or :multi_with_arrays
28
+
29
+ The ability of the resulting Object to be Marshalled(dump/load) can be preserved by merging configuration options
30
+ into the input params key ':enable_serialization' set to true. It defaults to false for speed purposes
31
+
32
+ Review the RSpec tests, and or review the README for more details.
33
+ EOF
34
+
35
+ spec.homepage = "https://github.com/skoona/skn_utils"
36
+ spec.license = "MIT"
37
+ spec.platform = Gem::Platform::RUBY
38
+ spec.files = `git ls-files -z`.split("\x0")
39
+ spec.executables = []
40
+ spec.test_files = spec.files.grep(%r{^(spec)/})
41
+ spec.require_paths = ["lib"]
42
+
43
+ spec.add_runtime_dependency 'activemodel', '~> 3.2'
44
+
45
+ spec.add_development_dependency "bundler", "~> 1.7"
46
+ spec.add_development_dependency "rake", "~> 10.0"
47
+ spec.add_development_dependency "rspec", '~> 3.0'
48
+ spec.add_development_dependency "pry"
49
+ spec.post_install_message = "Thanks for installing SknUtils, keep watch more utilities will be added!"
50
+ end
@@ -0,0 +1,94 @@
1
+ ##
2
+ # spec/lib/skn_utils/generic_bean_spec.rb
3
+ #
4
+
5
+ RSpec.describe SknUtils::GenericBean, "Generic Marshal'able Bean class " do
6
+ let(:object) {
7
+ SknUtils::GenericBean.new({one: "one",
8
+ two: "two",
9
+ three: {four: 4, five: 5, six: {seven: 7, eight: "eight" }},
10
+ four: {any_key: "any value"}, five: []}
11
+ )
12
+ }
13
+
14
+ context "Internal Operations, assuming :dept => :multi and enable_serialization => true" do
15
+ it "Creates an empty bean if no params are passed" do
16
+ is_expected.to be
17
+ end
18
+ it "Can be Marshalled after dynamically adding a key/value." do
19
+ expect { object.fifty = {any_key: "any value"} }.not_to raise_error
20
+ expect { object.sixty = 60 }.not_to raise_error
21
+ dmp = obj = ""
22
+ expect { dmp = Marshal.dump(object) }.not_to raise_error
23
+ expect { obj = Marshal.load(dmp) }.not_to raise_error
24
+ expect(obj).to be_a(SknUtils::GenericBean)
25
+ expect( object.fifty.any_key).to eql "any value"
26
+ expect( object.sixty).to eql 60
27
+ end
28
+ it "Initializes from a hash" do
29
+ expect(SknUtils::GenericBean.new({one: "one", two: "two"})).to be
30
+ end
31
+ it "Does not modify the base class, only singleton instance methods" do
32
+ obj1 = SknUtils::GenericBean.new({one: "one", two: "two"})
33
+ obj2 = SknUtils::GenericBean.new({three: "3", four: "4"})
34
+ expect(obj1.one).to eql "one"
35
+ expect(obj2.three).to eql "3"
36
+ expect(obj2.one?).to be false
37
+ expect(obj1.three?).to be false
38
+ expect { obj1.three }.to raise_error NoMethodError
39
+ expect { obj2.one }.to raise_error NoMethodError
40
+ end
41
+ it "Does not support - respond_to - methods, because it has no accessor methods" do
42
+ expect(object).not_to respond_to(:one)
43
+ expect(object.one).to eql "one"
44
+ end
45
+ it "nest objects if multi-level hash is given" do
46
+ expect(object.one).to be_eql("one")
47
+ expect(object.two).to be_eql("two")
48
+ expect(object.three).to be_a(SknUtils::GenericBean)
49
+ expect(object.three.five).to eq(5)
50
+ end
51
+ it "#attributes method returns a hash of all attributes and their values." do
52
+ expect(object.attributes).to be_a(Hash)
53
+ expect(object.attributes[:one]).to be_eql("one")
54
+ expect(object.attributes[:three]).to be_a(Hash)
55
+ end
56
+ end
57
+
58
+ shared_examples_for "retains initialization options" do
59
+ it "retains depth_level option flag" do
60
+ expect(@obj.depth_level).to eql(:multi)
61
+ end
62
+ it "retains serialization option flag" do
63
+ expect(@obj.serialization_required?).to be true
64
+ end
65
+ end
66
+
67
+ context "Basic Operations without marshaling " do
68
+ before :each do
69
+ @obj = object
70
+ end
71
+
72
+ it_behaves_like "retains initialization options"
73
+ it_behaves_like "marshalable ruby pojo"
74
+ end
75
+ context "Basic Operations after Yaml marshaling " do
76
+ before :each do
77
+ dmp = YAML::dump(object)
78
+ @obj = YAML::load(dmp)
79
+ end
80
+
81
+ it_behaves_like "retains initialization options"
82
+ it_behaves_like "marshalable ruby pojo"
83
+ end
84
+ context "Basic Operations after Marshal marshaling " do
85
+ before :each do
86
+ dmp = Marshal.dump(object)
87
+ @obj = Marshal.load(dmp)
88
+ end
89
+
90
+ it_behaves_like "retains initialization options"
91
+ it_behaves_like "marshalable ruby pojo"
92
+ end
93
+
94
+ end