Class: LucidWorks::Base

Inherits:
Object
  • Object
show all
Extended by:
ActiveModel::Callbacks, ActiveModel::Naming, ActiveModel::Translation, SimpleNaming
Includes:
ActiveModel::Conversion, ActiveModel::Validations, Associations, Utils::BoolConverter
Defined in:
lib/lucid_works/base.rb

Overview

LucidWorks::Base is our REST ORM foundation.

class Collection < LucidWorks::Base
end

When creating or retrieving objects, a parent LucidWorks::Base or LucidWorks::Server object must be supplied, e.g.

server = LucidWorks::Server.new('http://localhost:8888')

collection = Collection.find('collection1', :parent => server)

datasource = Datasource.find(1, :parent => collection)

This mechanism is used to build up the URI that will be used to manipulate the object in the REST API. For the above example:

datasource.uri -> http://localhost:8888/api/collections/collection1/datasources/1

If you are accessing models using associations, the parent argument is taken care of for you:

datasource = server.collection('collection1').datasource(1)

Constant Summary

Constants included from Utils::BoolConverter

Utils::BoolConverter::FALSE_VALUES, Utils::BoolConverter::TRUE_VALUES

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SimpleNaming

model_name

Methods included from Utils::BoolConverter

#to_bool

Constructor Details

#initialize(options) ⇒ Base

class methods

Raises:

  • (ArgumentError)


287
288
289
290
291
292
293
294
# File 'lib/lucid_works/base.rb', line 287

def initialize(options)
  raise ArgumentError.new("new requires a Hash") unless options.is_a?(Hash)
  @parent = self.class.extract_parent_from_options(options)
  @associations = {}
  @persisted = options.delete(:persisted) || singleton? || false
  @attributes = {}.with_indifferent_access
  load_attributes(options)
end

Class Attribute Details

.collection_nameObject

:nodoc:



48
49
50
# File 'lib/lucid_works/base.rb', line 48

def collection_name
  @collection_name
end

Instance Attribute Details

#attributesObject (readonly)

:nodoc:



43
44
45
# File 'lib/lucid_works/base.rb', line 43

def attributes
  @attributes
end

#idObject

:nodoc:



352
353
354
# File 'lib/lucid_works/base.rb', line 352

def id # :nodoc:
  @attributes[self.class.schema.primary_key]
end

#parentObject

:nodoc:



38
39
40
# File 'lib/lucid_works/base.rb', line 38

def parent
  @parent
end

#persisted=(value) ⇒ Object (writeonly)

:nodoc:



40
41
42
# File 'lib/lucid_works/base.rb', line 40

def persisted=(value)
  @persisted = value
end

#raw_responseObject

:nodoc:



41
42
43
# File 'lib/lucid_works/base.rb', line 41

def raw_response
  @raw_response
end

#response_dataObject

:nodoc:



42
43
44
# File 'lib/lucid_works/base.rb', line 42

def response_data
  @response_data
end

Class Method Details

.all(options) ⇒ Object

Shortcut for find(:all, options)



228
229
230
# File 'lib/lucid_works/base.rb', line 228

def all(options)
  find(:all, options)
end

.collection_url(parent) ⇒ Object

:nodoc:



263
264
265
# File 'lib/lucid_works/base.rb', line 263

def collection_url(parent) # :nodoc:
  "#{parent.uri}/#{collection_name}"
end

.create(*arguments) ⇒ Object

Create a new model.

MyObject.create(attr => value, ..., :parent => server_or_object)


79
80
81
# File 'lib/lucid_works/base.rb', line 79

def create(*arguments)
  new(*arguments).tap { |model| model.save }
end

.extract_parent_from_options(options) ⇒ Object

:nodoc:

Raises:

  • (ArgumentError)


271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/lucid_works/base.rb', line 271

def extract_parent_from_options(options) # :nodoc:
  # When this resource belongs_to another, allow the other's name as a key instead of :parent
  if respond_to?(:belongs_to_association_name) && options.has_key?(belongs_to_association_name)
    parent = options.delete(belongs_to_association_name)
  else
    parent = options.delete(:parent)
  end
  raise ArgumentError.new("parent is a required option (options were #{options.inspect}") unless parent
  unless parent.is_a?(Base) || parent.is_a?(Server)
    raise ArgumentError.new("parent must be a LucidWorks::Server or LucidWorks::Base")
  end
  parent
end

.find(*arguments) ⇒ Object

Retrieve one or more models from the server. Find may be called in the following ways:

Retrieve an entire collection:

find(:all, options)

Retrieve a single model

find(id, options)
find(:one, id, options)

Retrieve a singleton model

find(options)
find(:singleton, options)

Options

:parent - mandatory, another LucidWorks::Base instance or a LucidWorks::Server instance.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/lucid_works/base.rb', line 101

def find(*arguments)
  unless arguments.first.is_a?(Symbol)
    # We weren't called with a symbol, figure out what kind of find this is and re-call
    if singleton
      raise ArgumentError.new("wrong number of arguments (#{arguments.size} for 1)") unless arguments.size == 1
      return find(:singleton, *arguments)
    else
      raise ArgumentError.new("wrong number of arguments (#{arguments.size} for 2)") unless arguments.size == 2
      return find(:one, *arguments)
    end
  end

  kind_of_find = arguments.shift

  if kind_of_find == :one
    id = arguments.shift
    options = arguments.shift
    raise ArgumentError.new("find(:one) requires 3 arguments") if arguments.count > 0
  else
    options = arguments.first
  end

  parent = extract_parent_from_options(options)
  includes = options.delete(:include)
  order = options.delete(:order)

  url = case kind_of_find
    when :all;       collection_url(parent)
    when :one;       "#{parent.uri}/#{collection_name}/#{id}"
    when :singleton; "#{parent.uri}/#{singleton_name}"
  end

  raw_response = ActiveSupport::Notifications.instrument("lucid_works.request") do |payload|
    begin
      payload[:method]   = :get
      payload[:uri]      = url
      payload[:response] = RestClient.get(url)
    rescue RestClient::Exception => exception
      # Tack on what we were doing when we got the exception, then send it on up
      exception.message = "#{exception.message} while performing #{payload[:method]} #{payload[:uri]}"
      raise exception
    end
  end

  data = JSON.parse(raw_response)

  results =
    if kind_of_find == :all
      data.collect do |model_attributes|
        new(model_attributes.merge(:parent => parent, :persisted => true))
      end
    else
      attributes = data.is_a?(Hash) ? data : {}
      attributes.merge!(:parent => parent, :persisted => true)
      object = new(attributes)
      object.raw_response = raw_response
      object.response_data = data
      object
    end

  # Process :include options
  #
  # In the section, to reduce confusion we will use the terms owner/target instead of
  # parent/child to describe the association.
  #
  # If we are doing a find(:all) and this owner resource supports retrieval of all targets
  # in one request with owners/all/targets, get them and attach them to the original models'
  # association proxys.
  #
  results_array = [results].flatten # allow us to process a single result or array with the same code
  if includes && !results_array.empty?
    includes = [includes].flatten
    includes.each do |association_name|
      association_info = associations[association_name.to_s.singularize.to_sym]
      target_class     = class_eval(association_info[:class_name]) # get scoping right
      target_name      = association_info[:class_name].underscore

      if kind_of_find == :all && association_info[:retrieveable_en_masse]
        all_targets_path = "#{collection_url(parent)}/all/#{target_name}"
        raw_response = ActiveSupport::Notifications.instrument("lucid_works.request") do |payload|
          begin
            payload[:method]   = :get
            payload[:uri]      = all_targets_path
            payload[:response] = RestClient.get(all_targets_path)
          rescue RestClient::Exception => exception
            # Tack on what we were doing when we got the exception, then send it on up
            exception.message = "#{exception.message} while performing #{payload[:method]} #{payload[:uri]}"
            raise exception
          end
        end
        if association_info[:type] == :has_one
          all_targets_attributes = JSON.parse raw_response
          all_targets_attributes.each do |target_attributes|
            owner_id = target_attributes['id']
            owner = results_array.detect { |result| result.id == owner_id }
            if owner
              target = target_class.new(target_attributes.merge(:parent => owner, :persisted => true))
              association_proxy = owner.send(association_name)
              association_proxy.target = target
            end
          end
        elsif association_info[:type] == :has_many
          # [{"history":[...history_models...],"id":372},{"history":[...history_models...],"id":371}]
          JSON.parse(raw_response).each do |group_of_targets|
            owner_id = group_of_targets['id']
            owner = results_array.detect { |result| result.id == owner_id }
            targets = group_of_targets[target_name].collect do |target_attrs|
              target_class.new(target_attrs.merge(:parent => owner, :persisted => true))
            end
            association_proxy = owner.send(association_name)
            association_proxy.target = targets
          end
        end
      else # kind_of_find != :all || !retrieveable_en_masse
        results_array.each do |result|
          result.send("#{association_name}!")
        end
      end
    end
  end

  results.sort! { |a,b| a.send(order) <=> b.send(order) } if order
  results
end

.first(options) ⇒ Object



232
233
234
# File 'lib/lucid_works/base.rb', line 232

def first(options)
  find(:all, options).first
end

.human_attribute_value(attribute, value) ⇒ Object

Convert the attribute value to a string. If a schema has been defined for the model and a type has been defined for the attribute, it will have formatting applied as follows:

  • boolean will be converted to ‘yes’ or ‘no’

  • integer will be passed to number_with_delimter

If the attributes is listed in the schema as having :values =>, it will be translated.



248
249
250
# File 'lib/lucid_works/base.rb', line 248

def human_attribute_value(attribute, value)
  schema[attribute].human_value(value)
end

.last(options) ⇒ Object



236
237
238
# File 'lib/lucid_works/base.rb', line 236

def last(options)
  find(:all, options).last
end

.schema(&block) ⇒ Object

The attributes for a model are ascertained in on of two ways. Without a schema, the attributes list is automatically generated when the the object is retrieved from the server. Alternatively, you may define a schema for your object. Objects with attributes defined by schemas may have validations run against those attributes during creation.

Schema should be passed a block:

class Collection < LucidWorks::Base
  schema do
    attribute :name
  end

  validates_presence_of :name
end


66
67
68
69
70
71
72
73
# File 'lib/lucid_works/base.rb', line 66

def schema(&block)
  @schema ||= LucidWorks::Schema.new(self)
  if block_given?
    @schema.instance_eval(&block)
    @schema.create_accessors_for_attributes(self)
  end
  @schema
end

.singleton_nameObject

:nodoc:



267
268
269
# File 'lib/lucid_works/base.rb', line 267

def singleton_name # :nodoc:
  name.underscore.gsub(/^.*\//, '')
end

.to_select(attribute) ⇒ Object

For attributes listed in the schema as having :values, this will create an array-of-arrays suitable for use as options_for_select.



255
256
257
# File 'lib/lucid_works/base.rb', line 255

def to_select(attribute)
  schema[attribute].to_select
end

Instance Method Details

#collection_urlObject

:nodoc:



368
369
370
# File 'lib/lucid_works/base.rb', line 368

def collection_url # :nodoc:
  self.class.collection_url(parent)
end

#destroy(options = {}) ⇒ Object



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/lucid_works/base.rb', line 333

def destroy(options={})
  ActiveSupport::Notifications.instrument("lucid_works.request") do |payload|
    begin
      payload[:method]   = :delete
      payload[:uri]      = member_url
      payload[:options]  = options
      payload[:repsonse] = RestClient.delete(member_url, options)
    rescue RestClient::Conflict => e
      payload[:exception] = e
      attach_errors_to_model(e.response)
      false
    rescue RestClient::Exception => exception
      # Tack on what we were doing when we got the exception, then send it on up
      exception.message = "#{exception.message} while performing #{payload[:method]} #{payload[:uri]}"
      raise exception
    end
  end
end

#human_attribute_value(attribute) ⇒ Object

Convert the attribute value to a string. See LucidWorks::Base.human_attribute_values for details.



389
390
391
# File 'lib/lucid_works/base.rb', line 389

def human_attribute_value(attribute)
  self.class.human_attribute_value(attribute, send(attribute))
end

#inspectObject

:nodoc:



382
383
384
# File 'lib/lucid_works/base.rb', line 382

def inspect # :nodoc:
  "<#{self.class.name} " + @attributes.map { |k,v| "#{k}=#{v.inspect}" }.join(" ") + ">"
end

#member_urlObject Also known as: uri

:nodoc:



372
373
374
375
376
377
378
# File 'lib/lucid_works/base.rb', line 372

def member_url # :nodoc:
  if singleton?
    "#{parent.uri}/#{self.class.singleton_name}"
  else
    "#{parent.uri}/#{collection_name}/#{self.id}"
  end
end

#persisted?Boolean

Returns:

  • (Boolean)


360
361
362
# File 'lib/lucid_works/base.rb', line 360

def persisted?
  @persisted
end

#read_attribute_for_validation(key) ⇒ Object

:nodoc:



364
365
366
# File 'lib/lucid_works/base.rb', line 364

def read_attribute_for_validation(key) # :nodoc:
  @attributes[key]
end

#saveObject



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/lucid_works/base.rb', line 296

def save
  _run_save_callbacks do
    return false unless valid?
    ActiveSupport::Notifications.instrument("lucid_works.request") do |payload|
      method, uri = persisted? ? [:put, member_url] : [:post, collection_url]
      data = encode
      payload[:method] = method
      payload[:uri] = uri
      payload[:data] = data
      begin
        response = RestClient.send(method, uri, data, :content_type => :json)
        payload[:response] = response
        @persisted = true
        load_attributes_from_json_string(response)
        true
      rescue RestClient::Conflict,                   # 409
             RestClient::UnprocessableEntity,        # 422
             RestClient::InternalServerError => e    # 500
        payload[:exception] = e
        attach_errors_to_model(e.response)
        false
      rescue RestClient::Exception => exception
        # Tack on what we were doing when we got the exception, then send it on up
        exception.message = "#{exception.message} while performing #{payload[:method]} #{payload[:uri]}"
        raise exception
      end
    end
  end
end

#update_attributes(attrs_and_values) ⇒ Object



326
327
328
329
330
331
# File 'lib/lucid_works/base.rb', line 326

def update_attributes(attrs_and_values)
  attrs_and_values.each do |attr,value|
    self.send("#{attr}=", value)
  end
  save
end