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.



197
198
199
200
201
202
203
204
205
206
# File 'lib/montage_rails/base.rb', line 197

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

Instance Attribute Details

#persistedObject (readonly) Also known as: persisted?

Returns the value of attribute persisted.



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

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



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

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



144
145
146
# File 'lib/montage_rails/base.rb', line 144

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



150
151
152
# File 'lib/montage_rails/base.rb', line 150

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
# File 'lib/montage_rails/base.rb', line 129

def find_by_id(value)
  relation.where(id: value).first
end

.find_or_initialize_by(params = {}) ⇒ Object

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



137
138
139
140
# File 'lib/montage_rails/base.rb', line 137

def find_or_initialize_by(params = {})
  return nil if params.empty?
  relation.where(params).first || new(params)
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’



156
157
158
159
160
161
162
163
# File 'lib/montage_rails/base.rb', line 156

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



165
166
167
168
169
170
171
172
173
# File 'lib/montage_rails/base.rb', line 165

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:



175
176
177
# File 'lib/montage_rails/base.rb', line 175

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

#==(other) ⇒ Object



208
209
210
# File 'lib/montage_rails/base.rb', line 208

def ==(other)
  attributes == other.attributes
end

#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"'


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

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:



343
344
345
346
347
348
# File 'lib/montage_rails/base.rb', line 343

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



337
338
339
# File 'lib/montage_rails/base.rb', line 337

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

#destroyObject

Destroy the copy of this record from the database



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

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

  @persisted = false
  self
end

#dirty?Boolean

Checks if the attributes have changed, and returns true if they are “dirty”

Returns:



303
304
305
# File 'lib/montage_rails/base.rb', line 303

def dirty?
  @old_attributes != attributes
end

#inspectObject

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



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

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:



331
332
333
# File 'lib/montage_rails/base.rb', line 331

def new_record?
  !persisted?
end

#payloadObject

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



359
360
361
362
363
364
# File 'lib/montage_rails/base.rb', line 359

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

#reloadObject

Reload the current document



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

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



218
219
220
221
222
223
224
225
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
# File 'lib/montage_rails/base.rb', line 218

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

    if persisted?
      @current_method = "Update"

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

        initialize(attributes_from_response(@response))
      else
        return initialize(@old_attributes)
      end
    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



257
258
259
260
261
262
263
264
265
# File 'lib/montage_rails/base.rb', line 257

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



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

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 unless dirty?

  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



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

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