hashme 0.2.6 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6f50c8f90a959db106926caadbdaee800ed55073
4
- data.tar.gz: 422d08c967c35172445489d6716b1a799f53b299
2
+ SHA256:
3
+ metadata.gz: 157002395c66c0c5a0d4ffe7c13cf16f93a5ee016a8c42c9388362b7f80b65ae
4
+ data.tar.gz: '032850cb9db195516320a53afc10432d918b0f066fc4d0ed0cf7291216a336f5'
5
5
  SHA512:
6
- metadata.gz: 417b46455a892130eb7e4b8781dccfa7530ac10cf93d656863c78d3ebc5b59a2f81b8378acc9736944bd25062f37c65eb475ce8522b8af961b888802300318dd
7
- data.tar.gz: 0c8c5cd1b21333070e76c6e74f9c40095c4f48117086a72be98f61db7a0201f79cfb83ac5c3468e8ebaf6bf5d52e093bc5bb88671af8a08c737bd72b8feba15b
6
+ metadata.gz: 1c6820691be9c3b71e0e570d3bb4a37315e896e3203096017905750f9c18aa122039bc7a889dc67a1ff24ae2af7da2956803de806490e7224e3b40bda2de0f6c
7
+ data.tar.gz: 8e397827a0eb98c019e88b2a4d6155817693d50be1e90c33f6ff8a295dbd980198c24887daae41d68a3965fa50e96a987fa125e379c7503b8d0878c86da3b1fd
data/README.md CHANGED
@@ -120,6 +120,13 @@ u.errors.first # [:email, "can't be blank"]
120
120
 
121
121
  ## History
122
122
 
123
+ ### 0.3.0 - 2023-01-23
124
+
125
+ * Making `CastArray` provide the whole `Array` API by inherityin from it
126
+ * Removing default value fallback to fix bug with booleans
127
+ * Adding support for `URI` objects typecasting
128
+ * Adding `Boolean` as an alias of `TrueClass`
129
+
123
130
  ### 0.2.6 - 2017-09-12
124
131
 
125
132
  * Setting ActiveModel minimum to 4.0, adding travis config to test both 4.0 and 5.0. (@samlown)
@@ -158,7 +165,7 @@ u.errors.first # [:email, "can't be blank"]
158
165
  * Refactoring to use `class_attribute` for properties hash for improved inheritance.
159
166
 
160
167
  ### 0.1.1 - 2014-01-21
161
-
168
+
162
169
  * Fixing dependency on ActiveModel >= 3.0
163
170
 
164
171
  ### 0.1.0 - 2014-01-15
data/hashme.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency "activemodel", ">= 4.0"
22
22
 
23
- spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "bundler"
24
24
  spec.add_development_dependency "rake"
25
25
  spec.add_development_dependency "rspec"
26
26
  end
@@ -1,7 +1,7 @@
1
1
  module Hashme
2
2
  module Attributes
3
3
  include Enumerable
4
-
4
+
5
5
  extend Forwardable
6
6
 
7
7
  def_delegators :_attributes, :to_a,
@@ -2,60 +2,83 @@ require 'forwardable'
2
2
 
3
3
  module Hashme
4
4
 
5
- # The Hashme CastedArray is a special object wrapper that allows other Model's
6
- # or Objects to be stored in an array, but maintain casted ownership.
7
- #
8
- # Adding objects will automatically assign the Array's owner, as opposed
9
- # to the array itself.
10
- #
11
- class CastedArray
12
- extend Forwardable
13
-
5
+ # The Hashme CastedArray is a special Array that typecasts each item according to a given property
6
+ class CastedArray < Array
14
7
  attr_reader :property
15
8
 
16
- def_delegators :@_array,
17
- :to_a, :==, :eql?, :size,
18
- :first, :last, :at, :length,
19
- :each, :reject, :empty?, :map, :collect,
20
- :clear, :pop, :shift, :delete, :delete_at,
21
- :encode_json, :as_json, :to_json,
22
- :inspect, :any?
23
-
24
- def initialize(property, owner, values = [])
25
- @_array = []
9
+ def initialize(property, vals = [])
26
10
  @property = property
27
- if values.respond_to?(:each)
28
- values.each do |value|
29
- self.push(value)
30
- end
31
- end
11
+ super build_all(vals)
12
+ end
13
+
14
+ def <<(val)
15
+ super build(val)
16
+ end
17
+
18
+ def push(*vals)
19
+ super *build_all(vals)
20
+ end
21
+
22
+ alias append push
23
+
24
+ def concat(*arrays)
25
+ super *arrays.map { |array| build_all(array) }
32
26
  end
33
27
 
34
- def <<(obj)
35
- @_array << instantiate_and_build(obj)
28
+ def insert(index, *vals)
29
+ super index, *build_all(vals)
36
30
  end
37
31
 
38
- def push(obj)
39
- @_array.push(instantiate_and_build(obj))
32
+ def unshift(*vals)
33
+ super *build_all(vals)
40
34
  end
41
35
 
42
- def unshift(obj)
43
- @_array.unshift(instantiate_and_build(obj))
36
+ alias prepend unshift
37
+
38
+ def replace(array)
39
+ super build_all(array)
44
40
  end
45
-
46
- def [] index
47
- @_array[index]
41
+
42
+ def []=(*args)
43
+ args = args.dup
44
+ args[-1] = build(args[-1])
45
+ super *args
48
46
  end
49
47
 
50
- def []= index, obj
51
- @_array[index] = instantiate_and_build(obj)
48
+ def fill(*args, &block)
49
+ if block
50
+ super *args do |index|
51
+ val = block.call(index)
52
+ build(val)
53
+ end
54
+ else
55
+ args = args.dup
56
+ args[0] = build(args[0])
57
+ super *args
58
+ end
52
59
  end
53
-
60
+
61
+ def collect!(&block)
62
+ if block
63
+ super do |element|
64
+ val = block.call(element)
65
+ build(val)
66
+ end
67
+ else
68
+ super
69
+ end
70
+ end
71
+
72
+ alias map! collect!
73
+
54
74
  protected
55
75
 
56
- def instantiate_and_build(obj)
57
- property.build(self, obj)
76
+ def build_all(vals)
77
+ vals.map { |val| build(val) }
58
78
  end
59
79
 
80
+ def build(val)
81
+ property.build(val)
82
+ end
60
83
  end
61
84
  end
@@ -14,7 +14,7 @@ module Hashme
14
14
  def set_attribute(name, value)
15
15
  property = get_property(name)
16
16
  if property
17
- value = property.build(self, value)
17
+ value = property.build(value)
18
18
  if value.nil?
19
19
  delete(property.name)
20
20
  else
@@ -45,9 +45,9 @@ module Hashme
45
45
  set_attribute(key, value)
46
46
  end
47
47
  end
48
-
48
+
49
49
  private
50
-
50
+
51
51
  def get_property(name)
52
52
  self.class.properties[name.to_sym]
53
53
  end
@@ -68,7 +68,7 @@ module Hashme
68
68
  def define_property_methods(property)
69
69
  # Getter
70
70
  define_method(property.name) do
71
- get_attribute(property.name) || property.default
71
+ get_attribute(property.name)
72
72
  end
73
73
  # Setter
74
74
  define_method "#{property.name}=" do |value|
@@ -33,11 +33,11 @@ module Hashme
33
33
  end
34
34
 
35
35
  # Build a new object of the type defined by the property.
36
- def build(owner, value)
36
+ def build(value)
37
37
  if array && value.is_a?(Array)
38
- CastedArray.new(self, owner, value)
38
+ CastedArray.new(self, value)
39
39
  else
40
- PropertyCasting.cast(self, owner, value)
40
+ PropertyCasting.cast(self, value)
41
41
  end
42
42
  end
43
43
 
@@ -1,5 +1,8 @@
1
1
  module Hashme
2
2
 
3
+ # Alias TrueClass as Boolean, so that it can be used as property types
4
+ Boolean = TrueClass
5
+
3
6
  # Special property casting for reveiving data from sources without Ruby types, such as query
4
7
  # parameters from an API or JSON documents.
5
8
  #
@@ -7,10 +10,10 @@ module Hashme
7
10
  module PropertyCasting
8
11
  extend self
9
12
 
10
- CASTABLE_TYPES = [String, Symbol, TrueClass, Integer, Float, BigDecimal, DateTime, Time, Date, Class]
13
+ CASTABLE_TYPES = [String, Symbol, TrueClass, Integer, Float, BigDecimal, DateTime, Time, Date, Class, URI]
11
14
 
12
15
  # Automatically typecast the provided value into an instance of the provided type.
13
- def cast(property, owner, value)
16
+ def cast(property, value)
14
17
  return nil if value.nil?
15
18
  type = property.type
16
19
  if value.instance_of?(type) || type == Object
@@ -186,5 +189,12 @@ module Hashme
186
189
  nil
187
190
  end
188
191
 
192
+ # Typecast a value to URI
193
+ def typecast_to_uri(value)
194
+ URI(value)
195
+ rescue ArgumentError
196
+ nil
197
+ end
198
+
189
199
  end
190
200
  end
@@ -1,7 +1,7 @@
1
1
  module Hashme
2
2
  module Validations
3
3
  class CastedAttributeValidator < ActiveModel::EachValidator
4
-
4
+
5
5
  def validate_each(document, attribute, value)
6
6
  is_array = value.is_a?(Array) || value.is_a?(CastedArray)
7
7
  values = is_array ? value : [value]
@@ -1,3 +1,3 @@
1
1
  module Hashme
2
- VERSION = "0.2.6"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/hashme.rb CHANGED
@@ -2,6 +2,8 @@ require "hashme/version"
2
2
 
3
3
  # External dependencies
4
4
 
5
+ require "uri"
6
+
5
7
  require "active_model"
6
8
  require "active_model/naming"
7
9
  require "active_model/serialization"
@@ -1,14 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Hashme do
4
-
4
+
5
5
  before :each do
6
6
  @model = Class.new do
7
7
  include Hashme
8
8
  property :name, String
9
9
  end
10
10
  end
11
-
11
+
12
12
  describe '.build' do
13
13
  it "should create a Model and give a block to build it" do
14
14
  expect(@model).to receive(:call_in_block)
@@ -1,11 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Hashme::CastedArray do
4
-
5
- let :owner do
6
- double()
7
- end
8
-
9
4
  let :submodel do
10
5
  Class.new do
11
6
  include Hashme
@@ -13,43 +8,124 @@ describe Hashme::CastedArray do
13
8
  end
14
9
  end
15
10
 
16
- describe "#initialize" do
17
- let :property do
18
- Hashme::Property.new(:name, String)
19
- end
20
- before :each do
21
- @obj = Hashme::CastedArray.new(property, owner, ['test'])
22
- end
11
+ let :property do
12
+ Hashme::Property.new(:item, submodel)
13
+ end
14
+
15
+ subject do
16
+ Hashme::CastedArray.new(property, [{:name => 'test'}])
17
+ end
23
18
 
19
+ describe "#initialize" do
24
20
  it "should prepare array" do
25
- expect(@obj.length).to eql(1)
21
+ expect(subject.length).to eql(1)
26
22
  end
27
23
 
28
24
  it "should set property" do
29
- expect(@obj.property).to eql(property)
25
+ expect(subject.property).to eql(property)
30
26
  end
31
27
 
32
28
  it "should instantiate and cast each value" do
33
- expect(@obj.first).to eql("test")
34
- expect(@obj.first.class).to eql(String)
29
+ expect(subject.first.class).to eql(submodel)
30
+ expect(subject.first.name).to eql('test')
35
31
  end
36
32
  end
37
33
 
38
- describe "adding to array" do
39
- subject do
40
- Hashme::CastedArray.new(property, owner, [{:name => 'test'}])
41
- end
42
- let :property do
43
- Hashme::Property.new(:item, submodel)
44
- end
34
+ it "should cast items added to the array" do
35
+ subject << {:name => 'test2'}
36
+ expect(subject.last.class).to eql(submodel)
37
+ expect(subject.last.name).to eql('test2')
38
+ end
45
39
 
46
- it "should cast new items" do
47
- subject << {:name => 'test2'}
48
- expect(subject.last.class).to eql(submodel)
49
- expect(subject.first.name).to eql('test')
50
- expect(subject.last.name).to eql('test2')
51
- end
40
+ it "should cast items using `push`" do
41
+ subject.push({ :name => 'test2'}, { :name => 'test3' })
42
+ expect(subject.last.class).to eql(submodel)
43
+ expect(subject.last.name).to eql('test3')
44
+ end
45
+
46
+ it "should cast items using `concat`" do
47
+ subject.concat([{ :name => 'test2'}], [{ :name => 'test3' }])
48
+ expect(subject.last.class).to eql(submodel)
49
+ expect(subject.last.name).to eql('test3')
50
+ end
51
+
52
+ it "should cast items using `insert`" do
53
+ subject.insert(0, { :name => 'test2'}, { :name => 'test3' })
54
+ expect(subject[1].class).to eql(submodel)
55
+ expect(subject[1].name).to eql('test3')
56
+ end
57
+
58
+ it "should cast items using `unshift`" do
59
+ subject.unshift({ :name => 'test2'}, { :name => 'test3' })
60
+ expect(subject[1].class).to eql(submodel)
61
+ expect(subject[1].name).to eql('test3')
62
+ end
63
+
64
+ it "should cast items using `replace`" do
65
+ subject.replace([{ :name => 'test2'}, { :name => 'test3' }])
66
+ expect(subject[1].class).to eql(submodel)
67
+ expect(subject[1].name).to eql('test3')
68
+ end
69
+
70
+ it "should cast items using `[]=`" do
71
+ subject[5, 10] = { :name => 'test2'}
72
+ expect(subject[5].class).to eql(submodel)
73
+ expect(subject[5].name).to eql('test2')
74
+ end
75
+
76
+ it "should cast items using fill with block" do
77
+ subject.fill(0) { |index| { :name => "test#{index}" } }
78
+ expect(subject.last.class).to eql(submodel)
79
+ expect(subject.last.name).to eql('test0')
80
+ end
81
+
82
+ it "should cast items using fill without block" do
83
+ subject.fill({ :name => "test-filled" }, 0)
84
+ expect(subject.last.class).to eql(submodel)
85
+ expect(subject.last.name).to eql('test-filled')
86
+ end
87
+
88
+ it "should cast items using `collect!` with a block" do
89
+ subject.collect! { |item| { :name => "Mapped #{item.name}" } }
90
+ expect(subject.first.name).to eql('Mapped test')
91
+ end
52
92
 
93
+ it "should support Enumerable methods" do
94
+ map = subject.map(&:name)
95
+ expect(map).to be_an(Enumerable)
96
+ expect(map.first).to eql('test')
53
97
  end
54
98
 
99
+ it "should compare for equality with CastedArrays" do
100
+ same = Hashme::CastedArray.new(property, [{:name => 'test'}])
101
+ different = Hashme::CastedArray.new(property, [{:name => 'test2'}])
102
+
103
+ expect(subject == same).to be(true)
104
+ expect(subject.eql?(same)).to be(true)
105
+
106
+ expect(subject == different).to be(false)
107
+ expect(subject.eql?(different)).to be(false)
108
+ end
109
+
110
+ it "should compare for equality with Arrays" do
111
+ same = [submodel.new(:name => 'test')]
112
+ different = [submodel.new(:name => 'test2')]
113
+
114
+ expect(subject == same).to be(true)
115
+ expect(subject.eql?(same)).to be(true)
116
+
117
+ expect(subject == different).to be(false)
118
+ expect(subject.eql?(different)).to be(false)
119
+ end
120
+
121
+ it "should be compared for equality with Arrays" do
122
+ same = [submodel.new(:name => 'test')]
123
+ different = [submodel.new(:name => 'test2')]
124
+
125
+ expect(same == subject).to be(true)
126
+ expect(same.eql?(subject)).to be(true)
127
+
128
+ expect(different == same).to be(false)
129
+ expect(different.eql?(same)).to be(false)
130
+ end
55
131
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Hashme::Properties do
4
-
4
+
5
5
  before :all do
6
6
  @aux_model = Class.new do
7
7
  include Hashme
@@ -39,7 +39,7 @@ describe Hashme::Properties do
39
39
  it "should set and cast attribute with property" do
40
40
  property = model.send(:properties)[:name]
41
41
  name = "Fred Flinstone"
42
- expect(property).to receive(:build).with(obj, name).and_return(name)
42
+ expect(property).to receive(:build).with(name).and_return(name)
43
43
  obj.set_attribute(:name, name)
44
44
  expect(obj[:name]).to eql(name)
45
45
  end
@@ -111,12 +111,18 @@ describe Hashme::Properties do
111
111
  obj.desc = "test"
112
112
  expect(obj.desc).to eql("test")
113
113
  end
114
-
114
+
115
115
  it "should return nil on property with no default" do
116
116
  model.property :nickname, String
117
117
  expect(obj.nickname).to be_nil
118
118
  end
119
119
 
120
+ it 'should not return the default value when a Boolean is set to false' do
121
+ model.property :flag, TrueClass, :default => true
122
+ obj.flag = false
123
+ expect(obj.flag).to be(false)
124
+ end
125
+
120
126
  it "should create helper method with support for default values" do
121
127
  model.property :name, String, :default => "Sam"
122
128
  expect(obj.name).to eql("Sam")
@@ -12,10 +12,11 @@ class Course
12
12
  property :started_on, Date
13
13
  property :updated_at, DateTime
14
14
  property :active, TrueClass
15
- property :very_active, TrueClass
15
+ property :very_active, Boolean
16
16
  property :klass, Class
17
17
  property :currency, String, :default => 'EUR'
18
18
  property :symbol, Symbol
19
+ property :uri, URI
19
20
  end
20
21
 
21
22
  describe Hashme::PropertyCasting do
@@ -598,19 +599,19 @@ describe Hashme::PropertyCasting do
598
599
  end
599
600
  end
600
601
 
601
- describe 'when type primitive is a Boolean' do
602
+ describe 'when type primitive is a TrueClass' do
602
603
 
603
604
  [ true, 'true', 'TRUE', '1', 1, 't', 'T' ].each do |value|
604
605
  it "returns true when value is #{value.inspect}" do
605
606
  course.active = value
606
- expect(course['active']).to be_truthy
607
+ expect(course['active']).to be(true)
607
608
  end
608
609
  end
609
610
 
610
611
  [ false, 'false', 'FALSE', '0', 0, 'f', 'F' ].each do |value|
611
612
  it "returns false when value is #{value.inspect}" do
612
613
  course.active = value
613
- expect(course['active']).to be_falsey
614
+ expect(course['active']).to be(false)
614
615
  end
615
616
  end
616
617
 
@@ -620,5 +621,29 @@ describe Hashme::PropertyCasting do
620
621
  expect(course['active']).to be_nil
621
622
  end
622
623
  end
624
+
625
+ it "supports Boolean as an alias of TrueClass" do
626
+ course.very_active = 't'
627
+ expect(course['very_active']).to be(true)
628
+ end
629
+
630
+ end
631
+
632
+ describe 'when type primitive is an URI' do
633
+ it 'returns same value if an uri' do
634
+ value = URI('https://invopop.com')
635
+ course.uri = value
636
+ expect(course['uri']).to equal(value)
637
+ end
638
+
639
+ it 'returns an URI if parses as one' do
640
+ course.uri = 'https://invopop.com'
641
+ expect(course['uri']).to eql(URI('https://invopop.com'))
642
+ end
643
+
644
+ it 'does not typecast non-uri values' do
645
+ course.uri = 1234
646
+ expect(course['uri']).to be_nil
647
+ end
623
648
  end
624
649
  end
@@ -6,10 +6,6 @@ describe Hashme::Property do
6
6
  described_class
7
7
  end
8
8
 
9
- let :owner do
10
- double()
11
- end
12
-
13
9
  let :submodel do
14
10
  Class.new do
15
11
  include Hashme
@@ -61,7 +57,7 @@ describe Hashme::Property do
61
57
  context "without an array" do
62
58
  it "should build a new object" do
63
59
  prop = subject.new(:date, Time)
64
- obj = prop.build(owner, "2013-06-02T12:00:00Z")
60
+ obj = prop.build("2013-06-02T12:00:00Z")
65
61
  expect(obj.class).to eql(Time)
66
62
  expect(obj).to eql(Time.utc(2013, 6, 2, 12, 0, 0))
67
63
  end
@@ -70,13 +66,13 @@ describe Hashme::Property do
70
66
  context "with an array" do
71
67
  it "should convert regular array to casted array" do
72
68
  prop = subject.new(:dates, [Time])
73
- obj = prop.build(owner, ["2013-06-02T12:00:00"])
69
+ obj = prop.build(["2013-06-02T12:00:00"])
74
70
  expect(obj.class).to eql(Hashme::CastedArray)
75
71
  expect(obj.first.class).to eql(Time)
76
72
  end
77
73
  it "should handle complex objects" do
78
74
  prop = subject.new(:items, [submodel])
79
- obj = prop.build(owner, [{:name => 'test'}])
75
+ obj = prop.build([{:name => 'test'}])
80
76
  expect(obj.class).to eql(Hashme::CastedArray)
81
77
  expect(obj.first.class).to eql(submodel)
82
78
  expect(obj.first.name).to eql('test')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashme
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Lown
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-12 00:00:00.000000000 Z
11
+ date: 2023-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.3'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '1.3'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -119,8 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
119
  - !ruby/object:Gem::Version
120
120
  version: '0'
121
121
  requirements: []
122
- rubyforge_project:
123
- rubygems_version: 2.6.8
122
+ rubygems_version: 3.4.1
124
123
  signing_key:
125
124
  specification_version: 4
126
125
  summary: Easily define simple models that can be easily serialized and de-serialized.