Class: Puffs::SQLRelation

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

Overview

Queries made through Puffs::SQLObject return instance of SQLRelation

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ SQLRelation

Returns a new instance of SQLRelation.



46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/relation.rb', line 46

def initialize(options)
  defaults =
    {
      loaded: false,
      collection: []
    }

  merged_options = defaults.merge(options)

  @klass      = options[:klass]
  @collection = merged_options[:collection]
  @loaded     = merged_options[:loaded]
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object



138
139
140
# File 'lib/relation.rb', line 138

def method_missing(method, *args, &block)
  to_a.send(method, *args, &block)
end

Instance Attribute Details

#collectionObject (readonly)

Returns the value of attribute collection.



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

def collection
  @collection
end

#included_relationsObject

Returns the value of attribute included_relations.



44
45
46
# File 'lib/relation.rb', line 44

def included_relations
  @included_relations
end

#klassObject (readonly)

Returns the value of attribute klass.



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

def klass
  @klass
end

#loadedObject (readonly)

Returns the value of attribute loaded.



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

def loaded
  @loaded
end

#sql_countObject (readonly)

Returns the value of attribute sql_count.



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

def sql_count
  @sql_count
end

#sql_limitObject (readonly)

Returns the value of attribute sql_limit.



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

def sql_limit
  @sql_limit
end

Class Method Details

.build_association(base, included, method_name) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/relation.rb', line 4

def self.build_association(base, included, method_name)
  base.included_relations << included

  assoc_options = base.klass.assoc_options[method_name]
  has_many = assoc_options.class == HasManyOptions

  if has_many
    i_send = assoc_options.foreign_key
    b_send = assoc_options.primary_key
  else
    i_send = assoc_options.primary_key
    b_send = assoc_options.foreign_key
  end

  match = proc do
    selection = included.select do |i_sql_obj|
      i_sql_obj.send(i_send) == send(b_send)
    end

    associated = has_many ? selection : selection.first

    # After we find our values iteratively, we overwrite the method again
    # to the result values to reduce future lookup time to O(1).
    new_match = proc { associated }
    Puffs::SQLObject.define_singleton_method_by_proc(
      self, method_name, new_match)

    associated
  end

  # We overwrite the association method for each SQLObject in the
  # collection so that it points to our cached relation and
  # doesn't fire a query.
  base.collection.each do |b_sql_obj|
    Puffs::SQLObject.define_singleton_method_by_proc(
      b_sql_obj, method_name, match)
  end
end

Instance Method Details

#<<(item) ⇒ Object



60
61
62
# File 'lib/relation.rb', line 60

def <<(item)
  @collection << item if item.class == klass
end

#countObject



64
65
66
67
# File 'lib/relation.rb', line 64

def count
  @sql_count = true
  load
end

#includes(klass) ⇒ Object



73
74
75
76
# File 'lib/relation.rb', line 73

def includes(klass)
  includes_params << klass
  self
end

#includes_paramsObject



78
79
80
# File 'lib/relation.rb', line 78

def includes_params
  @includes_params ||= []
end

#limit(n) ⇒ Object



82
83
84
85
# File 'lib/relation.rb', line 82

def limit(n)
  @sql_limit = n
  self
end

#loadObject



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/relation.rb', line 87

def load
  unless loaded
    puts "LOADING #{table_name}"
    results = Puffs::DBConnection.execute(<<-SQL, sql_params[:values])
      SELECT
        #{sql_count ? 'COUNT(*)' : table_name.to_s + '.*'}
      FROM
        #{table_name}
      #{sql_params[:where]}
        #{sql_params[:params]}
      #{order_by_string}
      #{"LIMIT #{sql_limit}" if sql_limit};
    SQL

    results = sql_count ? results.first.values.first : parse_all(results)
  end

  results ||= self
  results = load_includes(results) unless includes_params.empty?
  results
end

#load_includes(relation) ⇒ Object



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
# File 'lib/relation.rb', line 109

def load_includes(relation)
  includes_params.each do |param|
    next unless relation.klass.has_association?(param)

    puts "LOADING #{param}"
    assoc = klass.assoc_options[param]
    f_k = assoc.foreign_key
    p_k = assoc.primary_key
    includes_table = assoc.table_name.to_s
    in_ids = relation.collection.map(&:id).join(', ')
    has_many = assoc.class == HasManyOptions

    results = Puffs::DBConnection.execute(<<-SQL)
      SELECT
        #{includes_table}.*
      FROM
        #{includes_table}
      WHERE
        #{includes_table}.#{has_many ? f_k : p_k}
      IN
        (#{in_ids});
    SQL
    included = assoc.model_class.parse_all(results)
    SQLRelation.build_association(relation, included, param)
  end

  relation
end

#order(params) ⇒ Object



142
143
144
145
146
147
148
149
# File 'lib/relation.rb', line 142

def order(params)
  if params.is_a?(Hash)
    order_params_hash.merge!(params)
  else
    order_params_hash[params] = :asc
  end
  self
end

#order_by_stringObject



155
156
157
158
159
160
161
# File 'lib/relation.rb', line 155

def order_by_string
  hash_string = order_params_hash.map do |column, asc_desc|
    "#{column} #{asc_desc.to_s.upcase}"
  end.join(', ')

  hash_string.empty? ? '' : "ORDER BY #{hash_string}"
end

#order_params_hashObject



151
152
153
# File 'lib/relation.rb', line 151

def order_params_hash
  @order_params_hash ||= {}
end

#parse_all(attributes) ⇒ Object



163
164
165
166
167
# File 'lib/relation.rb', line 163

def parse_all(attributes)
  klass.parse_all(attributes)
       .where(where_params_hash)
       .includes(includes_params)
end

#sql_paramsObject



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/relation.rb', line 169

def sql_params
  params = []
  values = []

  i = 1
  where_params_hash.map do |attribute, value|
    params << "#{attribute} = $#{i}"
    values << value
    i += 1
  end

  { params: params.join(' AND '),
    where: params.empty? ? nil : 'WHERE',
    values: values }
end

#table_nameObject



185
186
187
# File 'lib/relation.rb', line 185

def table_name
  klass.table_name
end

#to_aObject



189
190
191
# File 'lib/relation.rb', line 189

def to_a
  load.collection
end

#where(params) ⇒ Object



197
198
199
200
# File 'lib/relation.rb', line 197

def where(params)
  where_params_hash.merge!(params)
  self
end

#where_params_hashObject



193
194
195
# File 'lib/relation.rb', line 193

def where_params_hash
  @where_params_hash ||= {}
end