Class: MontageRails::Base

Inherits:
Object
  • Object
show all
Extended by:
ActiveModel::Callbacks, ActiveModel::Naming
Includes:
ActiveModel::Model
Defined in:
lib/montage_rails/base.rb,
lib/montage_rails/base/column.rb

Defined Under Namespace

Classes: Column

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params = {}) ⇒ Base

Returns a new instance of Base.



212
213
214
215
216
217
218
# File 'lib/montage_rails/base.rb', line 212

def initialize(params = {})
  initialize_columns
  @persisted = params[:persisted] ? params[:persisted] : false
  @current_method = "Load"
  @errors = ActiveModel::Errors.new(self)
  super(params)
end

Instance Attribute Details

#persistedObject (readonly) Also known as: persisted?

Returns the value of attribute persisted.



205
206
207
# File 'lib/montage_rails/base.rb', line 205

def persisted
  @persisted
end

Class Method Details

.allObject

Fetch all the documents



123
124
125
# File 'lib/montage_rails/base.rb', line 123

def all
  relation.to_a
end

.attributes_from_response(response) ⇒ Object



194
195
196
197
198
199
200
201
202
# File 'lib/montage_rails/base.rb', line 194

def attributes_from_response(response)
  case response.members
  when Montage::Documents then response.documents.first.attributes.merge(persisted: true)
  when Montage::Document then response.document.attributes.merge(persisted: true)
  when Montage::Errors then raise MontageAPIError, "There was an error with the Montage API: #{response.errors.attributes}"
  when Montage::Error then raise MontageAPIError, "There was an error with the Montage API: #{response.error.attributes}"
  else raise MontageAPIError, "There was an error with the Montage API, please try again."
  end
end

.belongs_to(table) ⇒ Object

Define a belongs_to relationship



66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/montage_rails/base.rb', line 66

def belongs_to(table)
  class_eval do
    define_method(table.to_s.tableize.singularize.to_sym) do
      table.to_s.classify.constantize.find_by_id(__send__(table.to_s.foreign_key))
    end

    define_method("#{table.to_s.tableize.singularize}=") do |record|
      self.__send__("#{table.to_s.foreign_key}=", record.id)
      self
    end
  end
end

.cacheObject

Define a new instance of the query cache



29
30
31
# File 'lib/montage_rails/base.rb', line 29

def cache
  @cache ||= QueryCache.new(MontageRails.no_caching)
end

.column_namesObject

Returns an array of the column names for the table



159
160
161
# File 'lib/montage_rails/base.rb', line 159

def column_names
  columns.map { |c| c.name }
end

.columnsObject

Returns an array of MontageRails::Base::Column’s for the schema



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/montage_rails/base.rb', line 99

def columns
  @columns ||= [].tap do |ary|
    response = connection.schema(table_name)

    return [] unless response.schema.respond_to?(:fields)

    ary << Column.new("id", "text", false)
    ary << Column.new("created_at", "datetime", false)
    ary << Column.new("updated_at", "datetime", false)

    response.schema.fields.each do |field|
      ary << Column.new(field["name"], field["datatype"], field["required"])

      instance_eval do
        define_singleton_method("find_by_#{field["name"]}") do |value|
          where("#{field["name"]} = '#{value}'").first
        end
      end
    end
  end
end

.create(params = {}) ⇒ Object

Initialize and save a new instance of the object



165
166
167
# File 'lib/montage_rails/base.rb', line 165

def create(params = {})
  new(params).save
end

.find_by_id(value) ⇒ Object Also known as: find

Find a record by the id



129
130
131
132
133
134
135
136
137
# File 'lib/montage_rails/base.rb', line 129

def find_by_id(value)
  response = cache.get_or_set_query(self, value) { connection.document(table_name, value) }

  if response.success?
    new(response.document.items.merge(persisted: true))
  else
    nil
  end
end

.find_or_initialize_by(params = {}) ⇒ Object

Find the record using the given params, or initialize a new one with those params



143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/montage_rails/base.rb', line 143

def find_or_initialize_by(params = {})
  return nil if params.empty?

  query = relation.where(params)

  response = cache.get_or_set_query(self, query) { connection.documents(table_name, query) }

  if response.success? && response.documents.any?
    new(attributes_from_response(response).merge(persisted: true))
  else
    new(params)
  end
end

.has_many(table, options = {}) ⇒ Object

Define a has_many relationship



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/montage_rails/base.rb', line 47

def has_many(table, options = {})
  class_eval do
    if options[:as]
      define_method(table.to_s.tableize.to_sym) do
        table.to_s.classify.constantize.where(
          "#{options[:as]}_id".to_sym => id,
          "#{options[:as]}_type".to_sym => self.class.name.demodulize
        )
      end
    else
      define_method(table.to_s.tableize.to_sym) do
        table.to_s.classify.constantize.where("#{self.class.table_name.demodulize.underscore.singularize.foreign_key} = #{id}")
      end
    end
  end
end

.inspectObject

Returns a string like ‘Post id:integer, title:string, body:text’



171
172
173
174
175
176
177
178
# File 'lib/montage_rails/base.rb', line 171

def inspect
  if self == Base
    super
  else
    attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
    "#{super}(#{attr_list})"
  end
end

.loggerObject

Hook into the Rails logger



35
36
37
# File 'lib/montage_rails/base.rb', line 35

def logger
  @logger ||= Rails.logger
end

.method_missing(method_name, *args, &block) ⇒ Object



180
181
182
183
184
185
186
187
188
# File 'lib/montage_rails/base.rb', line 180

def method_missing(method_name, *args, &block)
  __send__(:columns)

  if respond_to?(method_name.to_sym)
    __send__(method_name.to_sym, *args)
  else
    super(method_name, *args, &block)
  end
end

.relationObject

Setup a class level instance of the MontageRails::Relation object



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

def relation
  @relation = Relation.new(self)
end

.respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


190
191
192
# File 'lib/montage_rails/base.rb', line 190

def respond_to_missing?(method_name, include_private = false)
  __send__(:column_names).include?(method_name.to_s.split("_").first) || super(method_name, include_private)
end

.set_table_name(value) ⇒ Object Also known as: table_name=

Redefine the table name



87
88
89
90
91
92
93
# File 'lib/montage_rails/base.rb', line 87

def set_table_name(value)
  instance_eval do
    define_singleton_method(:table_name) do
      value
    end
  end
end

.table_nameObject

The pluralized table name used in API requests



81
82
83
# File 'lib/montage_rails/base.rb', line 81

def table_name
  self.name.demodulize.underscore.pluralize
end

Instance Method Details

#attribute_for_inspect(attr_name) ⇒ Object

Returns an #inspect-like string for the value of the attribute attr_name. String attributes are elided after 50 characters, and Date and Time attributes are returned in the :db format. Other attributes return the value of #inspect without modification.

person = Person.create!(:name => "David Heinemeier Hansson " * 3)

person.attribute_for_inspect(:name)
# => '"David Heinemeier Hansson David Heinemeier Hansson D..."'

person.attribute_for_inspect(:created_at)
# => '"2009-01-12 04:48:57"'


378
379
380
381
382
383
384
385
386
387
388
# File 'lib/montage_rails/base.rb', line 378

def attribute_for_inspect(attr_name)
  value = attributes[attr_name]

  if value.is_a?(String) && value.length > 50
    "#{value[0..50]}...".inspect
  elsif value.is_a?(Date) || value.is_a?(Time)
    %("#{value.to_s(:db)}")
  else
    value.inspect
  end
end

#attributes_valid?Boolean

Performs a check to ensure that required columns have a value

Returns:

  • (Boolean)


341
342
343
344
345
346
# File 'lib/montage_rails/base.rb', line 341

def attributes_valid?
  attributes.each do |key, value|
    next unless column_class = column_for(key.to_s)
    return false unless column_class.value_valid?(value)
  end
end

#column_for(name) ⇒ Object

Returns the Column class instance for the attribute passed in



335
336
337
# File 'lib/montage_rails/base.rb', line 335

def column_for(name)
  self.class.columns.select { |column| column.name == name }.first
end

#destroyObject

Destroy the copy of this record from the database



307
308
309
310
311
312
313
# File 'lib/montage_rails/base.rb', line 307

def destroy
  @current_method = "Delete"
  notify(self) { connection.delete_document(self.class.table_name, id) }

  @persisted = false
  self
end

#inspectObject

Returns the contents of the record as a nicely formatted string.



392
393
394
395
396
397
398
399
# File 'lib/montage_rails/base.rb', line 392

def inspect
  attributes_as_nice_string = self.class.column_names.collect { |name|
    if attributes[name.to_sym] || new_record?
      "#{name}: #{attribute_for_inspect(name.to_sym)}"
    end
  }.compact.join(", ")
  "#<#{self.class} #{attributes_as_nice_string}>"
end

#new_record?Boolean

Returns:

  • (Boolean)


329
330
331
# File 'lib/montage_rails/base.rb', line 329

def new_record?
  !persisted?
end

#payloadObject

Required for notifications to work, returns a payload suitable for the log subscriber



357
358
359
360
361
362
# File 'lib/montage_rails/base.rb', line 357

def payload
  {
    reql: reql_payload[@current_method],
    name: "#{self.class.name} #{@current_method}"
  }
end

#reloadObject

Reload the current document



317
318
319
320
321
322
323
324
325
326
327
# File 'lib/montage_rails/base.rb', line 317

def reload
  @current_method = "Load"

  response = notify(self) do
    connection.document(self.class.table_name, id)
  end

  initialize(attributes_from_response(response))
  @persisted = true
  self
end

#saveObject

Save the record to the database

Will return false if the attributes are not valid

Upon successful creation or update, will return true, otherwise returns false



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/montage_rails/base.rb', line 226

def save
  run_callbacks :save do
    return false unless valid? && attributes_valid?

    if persisted?
      @current_method = "Update"

      @response = notify(self) do
        connection.create_or_update_documents(self.class.table_name, [updateable_attributes(true)])
      end

      initialize(attributes_from_response(@response))
    else
      run_callbacks :create do
        @current_method = "Create"

        @response = notify(self) do
          connection.create_or_update_documents(self.class.table_name, [updateable_attributes(false)])
        end

        if @response.success?
          @persisted = true
          initialize(attributes_from_response(@response))
        else
          break
        end
      end
    end
  end

  @response.success? ? self : false
end

#save!Object

The bang method for save, which will raise an exception if saving is not successful



261
262
263
264
265
266
267
268
269
# File 'lib/montage_rails/base.rb', line 261

def save!
  response = save

  unless response
    raise MontageAPIError, "There was an error saving your data"
  end

  response
end

#update_attributes(params) ⇒ Object

Update the given attributes for the document

Returns false if the given attributes aren’t valid

Returns a copy of self if updating is successful



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/montage_rails/base.rb', line 277

def update_attributes(params)
  old_attributes = attributes.clone

  params.each do |key, value|
    if respond_to?(key.to_sym)
      coerced_value = column_for(key.to_s).coerce(value)
      send("#{key}=", coerced_value)
    end
  end

  return self if old_attributes == attributes

  if valid? && attributes_valid?
    @current_method = id.nil? ? "Create" : "Update"

    response = notify(self) do
      connection.create_or_update_documents(self.class.table_name, [updateable_attributes(!id.nil?)])
    end

    initialize(attributes_from_response(response))
    @persisted = true
    self
  else
    initialize(old_attributes)
    false
  end
end

#updateable_attributes(include_id = false) ⇒ Object

The attributes used to update the document



350
351
352
# File 'lib/montage_rails/base.rb', line 350

def updateable_attributes(include_id = false)
  include_id ? attributes.except(:created_at, :updated_at) : attributes.except(:created_at, :updated_at, :id)
end