Class: KnuVerse::Knufactor::Resource

Inherits:
Object
  • Object
show all
Extended by:
Helpers::ResourceClass
Includes:
Comparable, Helpers::Resource, Validations::Resource
Defined in:
lib/knuverse/knufactor/resource.rb

Overview

A generic API resource TODO: Thread safety

Direct Known Subclasses

KnuVerse::Knufactor::Resources::Client

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers::ResourceClass

determine_getter_names, determine_setter_names, human, i18n_key, immutable?, param_key, properties, route_key

Methods included from Helpers::Resource

#<=>, #datetime_from_params, #fresh?, #id, #id_property, #immutable?, #model_name, #new?, #path_for, #paths, #persisted?, #properties, #tainted?, #to_key, #to_model, #to_param, #update

Methods included from Validations::Resource

#validate_id, #validate_mutability

Constructor Details

#initialize(options = {}) ⇒ Resource

Returns a new instance of Resource.



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/knuverse/knufactor/resource.rb', line 149

def initialize(options = {})
  # TODO: better options validations
  raise Exceptions::InvalidOptions unless options.is_a?(Hash)

  @entity = options[:entity] || {}

  # Allows lazy-loading if we're told this is a lazy instance
  #  This means only the minimal attributes were fetched.
  #  This shouldn't be set by end-users.
  @lazy = options.key?(:lazy) ? options[:lazy] : false
  # This allows local, user-created instances to be differentiated from fetched
  # instances from the backend API. This shouldn't be set by end-users.
  @tainted = options.key?(:tainted) ? options[:tainted] : true
  # This is the API Client used to get data for this resource
  @api_client = options[:api_client] || APIClient.instance
  @errors = {}
  # A place to store which properties have been modified
  @modified_properties = []

  validate_mutability
  validate_id

  self.class.class_eval { gen_property_methods }
end

Instance Attribute Details

#clientObject

Returns the value of attribute client.



6
7
8
# File 'lib/knuverse/knufactor/resource.rb', line 6

def client
  @client
end

#errorsObject (readonly)

Returns the value of attribute errors.



7
8
9
# File 'lib/knuverse/knufactor/resource.rb', line 7

def errors
  @errors
end

Class Method Details

.all(options = {}) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/knuverse/knufactor/resource.rb', line 101

def self.all(options = {})
  # TODO: Add validations for options

  # TODO: add validation checks for the required pieces
  raise Exceptions::MissingPath unless path_for(:all)

  api_client = options[:api_client] || APIClient.instance

  root = name.split('::').last.en.plural.to_underscore
  # TODO: do something with lazy requests...

  ResourceCollection.new(
    api_client.get(path_for(:all))[root].collect do |record|
      new(
        entity: record,
        lazy: (options[:lazy] ? true : false),
        tainted: false,
        api_client: api_client
      )
    end,
    type: self,
    api_client: api_client
  )
end

.gen_getter_method(name, opts) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/knuverse/knufactor/resource.rb', line 57

def self.gen_getter_method(name, opts)
  determine_getter_names(name, opts).each do |method_name|
    define_method(method_name) do
      name_as_string = name.to_s
      reload if @lazy && !@entity.key?(name_as_string)

      # Casting values based on type
      case opts[:type]
      when :time
        if @entity[name_as_string] && !@entity[name_as_string].to_s.empty?
          Time.parse(@entity[name_as_string].to_s).utc
        end
      else
        @entity[name_as_string]
      end
    end
  end
end

.gen_property_methodsObject



90
91
92
93
94
95
96
97
98
99
# File 'lib/knuverse/knufactor/resource.rb', line 90

def self.gen_property_methods
  properties.each do |prop, opts|
    # Getter methods
    next if opts[:id_property]
    gen_getter_method(prop, opts) unless opts[:write_only]

    # Setter methods (don't make one for obviously read-only properties)
    gen_setter_method(prop, opts) unless opts[:read_only]
  end
end

.gen_setter_method(name, opts) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/knuverse/knufactor/resource.rb', line 76

def self.gen_setter_method(name, opts)
  determine_setter_names(name, opts).each do |method_name|
    define_method(method_name) do |value|
      raise Exceptions::ImmutableModification if immutable?
      if opts[:validate]
        raise Exceptions::InvalidArguments unless send("validate_#{name}".to_sym, value)
      end
      @entity[name.to_s] = opts[:type] == :time ? Time.parse(value.to_s).utc : value
      @tainted = true
      @modified_properties << name.to_sym
    end
  end
end

.get(id, options = {}) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/knuverse/knufactor/resource.rb', line 126

def self.get(id, options = {})
  # TODO: Add validations for options
  raise Exceptions::MissingPath unless path_for(:all)

  api_client = options[:api_client] || APIClient.instance

  new(
    entity: api_client.get("#{path_for(:all)}/#{id}"),
    lazy: false,
    tainted: false,
    api_client: api_client
  )
end

.immutable(status) ⇒ Object

Can this type of resource be changed client-side?



15
16
17
18
19
20
# File 'lib/knuverse/knufactor/resource.rb', line 15

def self.immutable(status)
  unless status.is_a?(TrueClass) || status.is_a?(FalseClass)
    raise Exceptions::InvalidArguments
  end
  @immutable = status
end

.path(kind, uri) ⇒ Object

Set the URI path for a resource method

Parameters:

  • kind (Symbol)

    how to refer to the URI

  • uri (String)

    an API URI to refer to later



42
43
44
# File 'lib/knuverse/knufactor/resource.rb', line 42

def self.path(kind, uri)
  paths[kind.to_sym] = uri
end

.path_for(kind) ⇒ Object



52
53
54
55
# File 'lib/knuverse/knufactor/resource.rb', line 52

def self.path_for(kind)
  guess = kind.to_sym == :all ? route_key : "#{route_key}/#{kind}"
  paths[kind.to_sym] || guess
end

.pathsHash{Symbol => String}

Create or set a class-level location to store URI paths for methods

Returns:

  • (Hash{Symbol => String})


48
49
50
# File 'lib/knuverse/knufactor/resource.rb', line 48

def self.paths
  @paths ||= {}
end

.property(name, options = {}) ⇒ Object

TODO:

add more validations on options and names

Define a property for a model



26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/knuverse/knufactor/resource.rb', line 26

def self.property(name, options = {})
  @properties ||= {}

  invalid_prop_names = [
    :>, :<, :'=', :class, :def,
    :%, :'!', :/, :'.', :'?', :*, :'{}',
    :'[]'
  ]

  raise(Exceptions::InvalidProperty) if invalid_prop_names.include?(name.to_sym)
  @properties[name.to_sym] = options
end

.where(attribute, value, options = {}) ⇒ Object



140
141
142
143
144
145
146
147
# File 'lib/knuverse/knufactor/resource.rb', line 140

def self.where(attribute, value, options = {})
  # TODO: validate incoming options
  options[:comparison] ||= value.is_a?(Regexp) ? :match : '=='
  api_client = options[:api_client] || APIClient.instance
  all(lazy: (options[:lazy] ? true : false), api_client: api_client).where(
    attribute, value, comparison: options[:comparison]
  )
end

Instance Method Details

#destroyObject



174
175
176
177
178
179
180
181
182
183
# File 'lib/knuverse/knufactor/resource.rb', line 174

def destroy
  raise Exceptions::ImmutableModification if immutable?
  unless new?
    @api_client.delete("#{path_for(:all)}/#{id}")
    @lazy = false
    @tainted = true
    @entity.delete('id')
  end
  true
end

#reloadObject



185
186
187
188
189
190
191
192
193
194
195
# File 'lib/knuverse/knufactor/resource.rb', line 185

def reload
  if new?
    # Can't reload a new resource
    false
  else
    @entity  = @api_client.get("#{path_for(:all)}/#{id}")
    @lazy    = false
    @tainted = false
    true
  end
end

#saveObject



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/knuverse/knufactor/resource.rb', line 197

def save
  saveable_data = @entity.select do |prop, value|
    pr = prop.to_sym
    go = properties.key?(pr) && !properties[pr][:read_only] && !value.nil?
    @modified_properties.uniq.include?(pr) if go
  end

  if new?
    @entity  = @api_client.post(path_for(:all).to_s, saveable_data)
    @lazy    = true
  else
    @api_client.put("#{path_for(:all)}/#{id}", saveable_data)
  end
  @tainted = false
  true
end