lazy_lazer 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 60a5387afe075f801b80682c550567d99da5a88e
4
- data.tar.gz: 39ebc9c0c59e3b5010d2e6d1e5dca543c2f0a494
3
+ metadata.gz: a24047c46cd370e8fb1d092129dacb1816f50ebc
4
+ data.tar.gz: 072b5a1b15857682e4317eb8f275685462199e3e
5
5
  SHA512:
6
- metadata.gz: 2726ff1abd8149b613a7cc6a689d4a1c3cf3e4f67924d59af9b02cac6186afc1a6de256498ea0edcff9e427303f1d6188eb672d222ced672d4b432d928c2ebc2
7
- data.tar.gz: c26457eb59bef476eac2c5de64c84a041bddfd212e40bfa901e7adb13dc22c578f76e3c9d79be89c1da1c4e97f5a4941632e0d180290985c619976ff425c6573
6
+ metadata.gz: de1fb716edf702586d593c085256d984ee3bd8cf0be8811381ac1d6eec10451146fa16f0e88f89a9561ad54d2242a255fc5351c1c6f0d961a2b01dc0e1c62f2d
7
+ data.tar.gz: ada536d3a04d7ce62beff22a4e2afb1d1cf38bf1aa51cae3f08e1e2bed9f2848cecb42b5af8d047c4cea6a2260864749ee9ac5975748f525bbc51c72f29749c8
data/.rubocop.yml CHANGED
@@ -20,3 +20,5 @@ Metrics/CyclomaticComplexity:
20
20
  Severity: refactor
21
21
  Metrics/PerceivedComplexity:
22
22
  Severity: refactor
23
+ Metrics/MethodLength:
24
+ Severity: refactor
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  **lazy lazer**
2
2
 
3
3
  **features**:
4
- - simple codebase (~100 lines of code)
4
+ - simple codebase (~110 lines of code, ~200 lines of tests)
5
5
  - doesn't inherit all of the Hash and Enumerable cruft
6
6
  - super lazy, doesn't even parse attributes until it's necessary
7
7
 
@@ -9,35 +9,26 @@
9
9
  class User
10
10
  include LazyLazer
11
11
 
12
- # User.new(first_name: 'John') #=> Error: missing `id`
13
- # User.new(id: 1).id? #=> true
14
12
  property :id, required: true
15
-
16
- # user = User.new(id: 1)
17
- # user.email #=> nil
18
- property :email, default: nil
19
-
20
- # user = User.new(id: 1)
21
- # user.language #=> :en_US
22
- property :language, default: :en_US
23
-
24
- # user = User.new(id: 1, first_name: 'John')
25
- # user.name #=> 'John'
26
- # user.first_name #=> NoMethodError: ...
27
- property :last_name, default: -> { %w[Doe Bloggs Hansen].sample }
28
-
29
- # user = User.new(id: 1, created_at: 1502834161)
30
- # user.created_at #=> 2017-08-15 22:56:13 +0100
31
- property :created_at, with: ->(time) { Time.at(time) }
32
-
33
- # user = User.new(id: 1, age: '45')
34
- # user.age #=> 45
13
+ property :email, default: 'unknown@example.com'
14
+ property :created_at, from: 'creation_time_utc', with: ->(time) { Time.at(time) }
35
15
  property :age, with: :to_i
36
16
 
37
- def reload
38
- update_attributes!(email: "#{last_name}@gmail.com") # update your attributes
17
+ property :favorite_ice_cream
18
+
19
+ def lazer_reload
39
20
  self.fully_loaded = true # mark model as fully updated
40
- self # a rails convention, totally optional
21
+ { favorite_ice_cream: %w[vanilla strawberry chocolate].sample }
41
22
  end
42
23
  end
24
+
25
+ user = User.new(id: 152, creation_time_utc: 1500000000, age: '21')
26
+
27
+ user.id #=> 152
28
+ user.email #=> "unknown@example.com"
29
+ user.created_at #=> 2017-07-14 03:40:00 +0100
30
+ user.age #=> 21
31
+
32
+ user.favorite_ice_cream #=> "chocolate"
33
+ user.reload.favorite_ice_cream #=> "vanilla"
43
34
  ```
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LazyLazer
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/lazy_lazer.rb CHANGED
@@ -2,9 +2,8 @@
2
2
 
3
3
  require_relative 'lazy_lazer/version'
4
4
  require_relative 'lazy_lazer/errors'
5
- require_relative 'lazy_lazer/utilities'
6
5
 
7
- # LazyLazer is a lazy loading model.
6
+ # LazyLazer
8
7
  module LazyLazer
9
8
  # Hook into `include LazyLazer`.
10
9
  # @param [Module] base the object to include the methods in
@@ -12,8 +11,22 @@ module LazyLazer
12
11
  def self.included(base)
13
12
  base.extend(ClassMethods)
14
13
  base.include(InstanceMethods)
15
- base.instance_variable_set(:@_lazer_properties, {})
16
- base.instance_variable_set(:@_lazer_required_properties, [])
14
+ base.instance_variable_set(
15
+ :@lazer_metadata,
16
+ properties: [],
17
+ required: {},
18
+ default: {},
19
+ from: {},
20
+ with: {}
21
+ )
22
+ end
23
+
24
+ # Get the source key from an instance
25
+ # @param [Object] instance the instance
26
+ # @param [Symbol] key the property key
27
+ # @return [Symbol] the source key if found or the passed key if not found
28
+ def self.source_key(instance, key)
29
+ instance.class.lazer_metadata[:from].fetch(key, key)
17
30
  end
18
31
 
19
32
  # The methods to extend the class with.
@@ -22,78 +35,118 @@ module LazyLazer
22
35
  # @param [Class] klass the subclass
23
36
  # @return [void]
24
37
  def inherited(klass)
25
- klass.instance_variable_set(:@_lazer_properties, @_lazer_properties)
26
- klass.instance_variable_set(:@_lazer_required_properties, @_lazer_required_properties)
38
+ klass.instance_variable_set(:@lazer_metadata, @lazer_metadata)
27
39
  end
28
40
 
29
- # @return [Hash<Symbol, Hash>] defined properties and their options
30
- def properties
31
- @_lazer_properties
41
+ # @return [Hash<Symbol, Hash>] the lazer configuration for this model
42
+ def lazer_metadata
43
+ @lazer_metadata
32
44
  end
33
45
 
34
46
  # Define a property.
35
47
  # @param [Symbol] name the name of the property method
36
48
  # @param [Hash] options the options to create the property with
37
- # @option options [Boolean] :required (false) whether existence of this
38
- # property should be checked on model creation
39
- # @option options [Symbol, Array<Symbol>] :from (name) the key(s) to get
40
- # the value of the property from
41
- # @option options [Object, Proc] :default the default value to return if
42
- # not provided
43
- # @option options [Proc, Symbol] :with an optional transformation to apply
44
- # to the value of the key
49
+ # @option options [Boolean] :required (false) whether existence of this property should be
50
+ # checked on model creation
51
+ # @option options [Object, Proc] :default the default value to return if not provided
52
+ # @option options [Symbol] :from the key in the source object to get the property from
53
+ # @option options [Proc, Symbol] :with an optional transformation to apply to the value
54
+ # @note both :required and :default can't be provided
55
+ # @return [Symbol] the name of the created property
45
56
  def property(name, **options)
57
+ raise 'both :required and :default cannot be given' if options[:required] && options[:default]
46
58
  sym_name = name.to_sym
47
- properties[sym_name] = options
48
- @_lazer_required_properties << sym_name if options[:required]
49
- define_method(name) { read_attribute(name) }
59
+ @lazer_metadata[:properties] << sym_name
60
+ options.each_pair { |option, value| @lazer_metadata[option][sym_name] = value }
61
+ define_method(sym_name) { read_attribute(sym_name) }
50
62
  end
51
63
  end
52
64
 
53
- # The base model class. This could be included directly.
65
+ # The methods to extend the instance with.
54
66
  module InstanceMethods
55
- # Initializer.
56
- #
67
+ # Create a new instance of the class from a set of source attributes.
57
68
  # @param [Hash] attributes the model attributes
58
69
  # @return [void]
59
70
  def initialize(attributes = {})
60
- # Check all required attributes.
61
- self.class.instance_variable_get(:@_lazer_required_properties).each do |prop|
62
- raise RequiredAttribute, "#{self} requires `#{prop}`" unless attributes.key?(prop)
71
+ # Check that all required attributes exist.
72
+ self.class.lazer_metadata[:required].keys.each do |property|
73
+ key = LazyLazer.source_key(self, property)
74
+ raise RequiredAttribute, "#{self} requires `#{key}`" unless attributes.key?(key)
63
75
  end
64
76
 
65
- @_lazer_attribute_source = attributes.dup
66
- @_lazer_attribute_cache = {}
77
+ @_lazer_source = attributes.dup
78
+ @_lazer_cache = {}
67
79
  end
68
80
 
81
+ # Converts all the attributes that haven't been converted yet and returns the final hash.
69
82
  # @param [Boolean] strict whether to fully load all attributes
70
83
  # @return [Hash] a hash representation of the model
71
84
  def to_h(strict = true)
72
85
  if strict
73
- remaining = @_lazer_attribute_source.keys - @_lazer_attribute_cache.keys
74
- remaining.each do |key|
75
- @_lazer_attribute_cache[key] = read_attribute(key)
76
- end
86
+ todo = self.class.lazer_metadata[:properties] - @_lazer_cache.keys
87
+ todo.each { |k| read_attribute(k) }
77
88
  end
78
- @_lazer_attribute_cache
89
+ @_lazer_cache
79
90
  end
80
91
 
81
- # Reload the object.
82
- def reload; end
92
+ # @abstract Provides reloading behaviour for lazy loading.
93
+ # @return [Hash] the result of reloading the hash
94
+ # @note if necessary, subclasses can make this method private, so this isn't tested.
95
+ def lazer_reload
96
+ self.fully_loaded = true
97
+ {}
98
+ end
99
+
100
+ # Reload the object. Calls {#lazer_reload}, then merges the results into the internal store.
101
+ # Also evicts the internal cache of the new keys.
102
+ # @return [self] the updated object
103
+ def reload
104
+ new_attributes = lazer_reload
105
+ @_lazer_source.merge!(new_attributes)
106
+ @_lazer_cache = {}
107
+ self
108
+ end
83
109
 
84
110
  # Return the value of the attribute.
85
111
  # @param [Symbol] name the attribute name
86
112
  # @raise MissingAttribute if the key was not found
87
113
  def read_attribute(name)
88
- return @_lazer_attribute_cache[name] if @_lazer_attribute_cache.key?(name)
89
- reload if self.class.properties.key?(name) && !fully_loaded?
90
- options = self.class.properties.fetch(name, {})
114
+ # Returns the cached attribute.
115
+ return @_lazer_cache[name] if @_lazer_cache.key?(name)
116
+
117
+ # Converts the property into the lookup key.
118
+ source_key = LazyLazer.source_key(self, name)
91
119
 
92
- if !@_lazer_attribute_source.key?(name) && !options.key?(:default)
93
- raise MissingAttribute, "#{name} is missing for #{self}"
120
+ # Reloads if a key that should be there isn't.
121
+ reload if !@_lazer_source.key?(source_key) &&
122
+ self.class.lazer_metadata[:required].include?(name) &&
123
+ !fully_loaded?
124
+
125
+ # Complains if even after reloading, the key is missing (and there's no default).
126
+ if !@_lazer_source.key?(source_key) && !self.class.lazer_metadata[:default].key?(name)
127
+ raise MissingAttribute, "`#{source_key}` missing for #{self}"
128
+ end
129
+
130
+ # Gets the value or gets the default.
131
+ value_or_default = @_lazer_source.fetch(source_key) do
132
+ default = self.class.lazer_metadata[:default][name]
133
+ default.is_a?(Proc) ? instance_exec(&default) : default
94
134
  end
95
- uncoerced = Utilities.lookup_default(@_lazer_attribute_source, name, options[:default])
96
- Utilities.transform_value(uncoerced, options[:with])
135
+
136
+ # Transforms the result using :with, if found.
137
+ transformer = self.class.lazer_metadata[:with][name]
138
+ coerced =
139
+ case transformer
140
+ when Symbol
141
+ value_or_default.public_send(transformer)
142
+ when Proc
143
+ instance_exec(value_or_default, &transformer)
144
+ else
145
+ value_or_default
146
+ end
147
+
148
+ # Add to cache and return the result.
149
+ @_lazer_cache[name] = coerced
97
150
  end
98
151
 
99
152
  # Return the value of the attribute, returning nil if not found
@@ -108,7 +161,7 @@ module LazyLazer
108
161
  # @param [Symbol] attribute the attribute to update
109
162
  # @param [Object] value the new value
110
163
  def write_attribute(attribute, value)
111
- @_lazer_attribute_cache[attribute] = value
164
+ @_lazer_cache[attribute] = value
112
165
  end
113
166
 
114
167
  # Update multiple attributes at once.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lazy_lazer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Avinash Dwarapu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-08-17 00:00:00.000000000 Z
11
+ date: 2017-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -130,7 +130,6 @@ files:
130
130
  - lazy_lazer.gemspec
131
131
  - lib/lazy_lazer.rb
132
132
  - lib/lazy_lazer/errors.rb
133
- - lib/lazy_lazer/utilities.rb
134
133
  - lib/lazy_lazer/version.rb
135
134
  homepage: https://github.com/avinashbot/lazy_lazer
136
135
  licenses:
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LazyLazer
4
- # Utility methods.
5
- # @api private
6
- module Utilities
7
- # Get a value from a hash, calling the default if needed.
8
- # @param [Hash] source the hash to lookup
9
- # @param [Symbol] key the key to lookup
10
- # @param [Proc, Object] default the default value or Proc to call
11
- # @return the object or the default value
12
- def self.lookup_default(source, key, default)
13
- return source[key] if source.key?(key)
14
- return default.call if default.is_a?(Proc)
15
- default
16
- end
17
-
18
- # Transforms a value using a "transformer" supplied using :with.
19
- # @param [Object] value the value to transform
20
- # @param [nil, Symbol, Proc] transformer the transformation method
21
- # @param [Object] context the context to run the proc in
22
- # @return [Object] the result of applying the transformation to the value
23
- def self.transform_value(value, transformer)
24
- case transformer
25
- when nil
26
- value
27
- when Symbol
28
- value.public_send(transformer)
29
- when Proc
30
- transformer.call(value)
31
- end
32
- end
33
- end
34
- end