Class: Passwordstate::Resource

Inherits:
Object
  • Object
show all
Defined in:
lib/passwordstate/resource.rb

Overview

A simple resource DSL helper

rubocop:disable Metrics/ClassLength This DSL class will be large

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data) ⇒ Resource

Returns a new instance of Resource.



35
36
37
38
39
# File 'lib/passwordstate/resource.rb', line 35

def initialize(data)
  @client = data.delete :_client
  set! data, store_old: false
  old
end

Instance Attribute Details

#clientObject (readonly)

Returns the value of attribute client.



8
9
10
# File 'lib/passwordstate/resource.rb', line 8

def client
  @client
end

Class Method Details

.acceptable_methods(*meths) ⇒ Object

Get/Set acceptable CRUD methods for the resource type



218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/passwordstate/resource.rb', line 218

def acceptable_methods(*meths)
  if meths.empty?
    @acceptable_methods || %i[post get put delete]
  elsif meths.count == 1 && meths.to_s.upcase == meths.to_s
    @acceptable_methods = []
    meths = meths.first.to_s
    @acceptable_methods << :post if meths.include? 'C'
    @acceptable_methods << :get if meths.include? 'R'
    @acceptable_methods << :put if meths.include? 'U'
    @acceptable_methods << :delete if meths.include? 'D'
  else
    @acceptable_methods = meths
  end
end

.all(client, **query) ⇒ Object Also known as: search

Retrieve all accessible instances of the resource type, requires the method :get

Raises:



52
53
54
55
56
57
58
59
60
61
62
# File 'lib/passwordstate/resource.rb', line 52

def self.all(client, **query)
  raise NotAcceptableError, "Read is not implemented for #{self}" unless acceptable_methods.include? :get

  path = query.fetch(:_api_path, api_path)
  reason = query.delete(:_reason)
  query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }

  [client.request(:get, path, query: query, reason: reason)].flatten.map do |object|
    new object.merge(_client: client)
  end
end

.api_path(path = nil) ⇒ Object

Get/Set the API path for the resource type



200
201
202
203
# File 'lib/passwordstate/resource.rb', line 200

def api_path(path = nil)
  @api_path = path unless path.nil?
  @api_path
end

.available?(_client) ⇒ Boolean

Check if the resource type is available on the connected Passwordstate server

Returns:

  • (Boolean)


47
48
49
# File 'lib/passwordstate/resource.rb', line 47

def self.available?(_client)
  true
end

.delete(client, object, **query) ⇒ Object

Delete an instance of the resource type, requires the method :delete

Raises:



109
110
111
112
113
114
115
116
117
118
# File 'lib/passwordstate/resource.rb', line 109

def self.delete(client, object, **query)
  raise NotAcceptableError, "Delete is not implemented for #{self}" unless acceptable_methods.include? :delete

  path = query.fetch(:_api_path, api_path)
  reason = query.delete(:_reason)
  query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }

  object = object.send(object.class.send(index_field)) if object.is_a? Resource
  client.request :delete, "#{path}/#{object}", query: query, reason: reason
end

.get(client, object, **query) ⇒ Object

Retrieve a specific instance of the resource type, requires the method :get

Raises:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/passwordstate/resource.rb', line 65

def self.get(client, object, **query)
  raise NotAcceptableError, "Read is not implemented for #{self}" unless acceptable_methods.include? :get

  object = object.send(object.class.send(index_field)) if object.is_a? Resource

  return new _client: client, index_field => object if query[:_bare]

  path = query.fetch(:_api_path, api_path)
  reason = query.delete(:_reason)
  query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }

  resp = client.request(:get, "#{path}/#{object}", query: query, reason: reason).map do |data|
    new data.merge(_client: client)
  end
  return resp.first if resp.one? || resp.empty?

  resp
end

.index_field(field = nil) ⇒ Object

Get/Set the index field for the resource type



206
207
208
209
# File 'lib/passwordstate/resource.rb', line 206

def index_field(field = nil)
  @index_field = field unless field.nil?
  @index_field
end

.nil_as_string(opt = nil) ⇒ Object

Get/Set whether the resource type requires nil values to be handled as the empty string



212
213
214
215
# File 'lib/passwordstate/resource.rb', line 212

def nil_as_string(opt = nil)
  @nil_as_string = opt unless opt.nil?
  @nil_as_string
end

.passwordstate_to_ruby_field(field) ⇒ Object

Convert a Passwordstate field name into Ruby syntax



239
240
241
242
# File 'lib/passwordstate/resource.rb', line 239

def passwordstate_to_ruby_field(field)
  opts = send(:field_options).find { |(_k, v)| v[:name] == field }
  opts&.first || field.to_s.snake_case.to_sym
end

.passwordstateify_hash(hash) ⇒ Object

Convert a hash from Ruby syntax to Passwordstate syntax



234
235
236
# File 'lib/passwordstate/resource.rb', line 234

def passwordstateify_hash(hash)
  hash.transform_keys { |k| ruby_to_passwordstate_field(k) }
end

.post(client, data, **query) ⇒ Object

Create a new instance of the resource type, requires the method :post

Raises:



85
86
87
88
89
90
91
92
93
94
# File 'lib/passwordstate/resource.rb', line 85

def self.post(client, data, **query)
  raise NotAcceptableError, "Create is not implemented for #{self}" unless acceptable_methods.include? :post

  path = query.fetch(:_api_path, api_path)
  reason = query.delete(:_reason)
  data = passwordstateify_hash data
  query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }

  new [client.request(:post, path, body: data, query: query, reason: reason)].flatten.first.merge(_client: client)
end

.put(client, data, **query) ⇒ Object

Push new data to an instance of the resource type, requires the method :put

Raises:



97
98
99
100
101
102
103
104
105
106
# File 'lib/passwordstate/resource.rb', line 97

def self.put(client, data, **query)
  raise NotAcceptableError, "Update is not implemented for #{self}" unless acceptable_methods.include? :put

  path = query.fetch(:_api_path, api_path)
  reason = query.delete(:_reason)
  data = passwordstateify_hash data
  query = passwordstateify_hash query.reject { |k| k.to_s.start_with? '_' }

  client.request :put, path, body: data, query: query, reason: reason
end

.ruby_to_passwordstate_field(field) ⇒ Object

Convert a Ruby field name into Passwordstate syntax



245
246
247
# File 'lib/passwordstate/resource.rb', line 245

def ruby_to_passwordstate_field(field)
  send(:field_options)[field]&.[](:name) || field.to_s.camel_case
end

Instance Method Details

#attributes(ignore_redact: true, atify: false, **opts) ⇒ Object

Get a hash of all active attributes on the resource instance

Parameters:

  • ignore_redact (Boolean) (defaults to: true)

    Should any normally redacted fields be displayed in clear-text

  • atify (Boolean) (defaults to: false)

    Should the resulting hash be returned with instance variable names (‘@’-prefixed)

  • opts (Hash)

    a customizable set of options

Options Hash (**opts):

  • nil_as_string (Boolean) — default: resource dependent

    Should nil values be treated as the empty string



125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/passwordstate/resource.rb', line 125

def attributes(ignore_redact: true, atify: false, **opts)
  nil_as_string = opts.fetch(:nil_as_string, self.class.nil_as_string)
  (self.class.send(:accessor_field_names) + self.class.send(:read_field_names) + self.class.send(:write_field_names)).to_h do |field|
    redact = self.class.send(:field_options)[field]&.fetch(:redact, false) && !ignore_redact
    at_field = "@#{field}".to_sym
    field = at_field if atify
    value = instance_variable_get(at_field) unless redact
    value = '[ REDACTED ]' if redact
    value = '' if value.nil? && nil_as_string
    [field, value]
  end.compact
end

#delete(**query) ⇒ Resource

Delete the object

Returns:



31
32
33
# File 'lib/passwordstate/resource.rb', line 31

def delete(**query)
  self.class.delete(client, send(self.class.index_field), **query)
end

#get(**query) ⇒ Resource

Update the object based off of up-to-date upstream data

Returns:



12
13
14
# File 'lib/passwordstate/resource.rb', line 12

def get(**query)
  set! self.class.get(client, send(self.class.index_field), **query)
end

#post(body = {}, **query) ⇒ Resource

Create the object based off of provided information

Returns:



25
26
27
# File 'lib/passwordstate/resource.rb', line 25

def post(body = {}, **query)
  set! self.class.post(client, attributes.merge(body), **query)
end

#pretty_print(pp) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/passwordstate/resource.rb', line 138

def pretty_print(pp)
  pp.object_address_group(self) do
    attrs = attributes(atify: true, nil_as_string: false, ignore_redact: false).compact
    pp.seplist(attrs.keys.sort, -> { pp.text ',' }) do |v|
      pp.breakable
      v = v.to_s if v.is_a? Symbol
      pp.text v
      pp.text '='
      pp.group(1) do
        pp.breakable ''
        pp.pp(attrs[v.to_sym])
      end
    end
  end
end

#put(body = {}, **query) ⇒ Resource

Push any unapplied changes to the object

Returns:



18
19
20
21
# File 'lib/passwordstate/resource.rb', line 18

def put(body = {}, **query)
  to_send = modified.merge(self.class.index_field => send(self.class.index_field))
  set! self.class.put(client, to_send.merge(body), **query).first
end

#stored?Boolean

Is the object stored on the Passwordstate server

Returns:

  • (Boolean)


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

def stored?
  !send(self.class.index_field).nil?
end