skn_utils 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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