empty_eye 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +6 -1
- data/lib/empty_eye/active_record/base.rb +41 -85
- data/lib/empty_eye/associations/shard_association_scope.rb +1 -1
- data/lib/empty_eye/associations/shard_has_one_association.rb +1 -1
- data/lib/empty_eye/persistence.rb +4 -4
- data/lib/empty_eye/{primary_view_extension.rb → primary_shard.rb} +30 -43
- data/lib/empty_eye/relation.rb +2 -65
- data/lib/empty_eye/shard.rb +86 -111
- data/lib/empty_eye/shard_collection.rb +192 -0
- data/lib/empty_eye/shard_wrangler.rb +300 -0
- data/lib/empty_eye/version.rb +1 -1
- data/lib/empty_eye.rb +5 -4
- data/spec/spec_helper.rb +0 -6
- metadata +7 -7
- data/lib/empty_eye/view_extension.rb +0 -114
- data/lib/empty_eye/view_extension_collection.rb +0 -227
@@ -0,0 +1,192 @@
|
|
1
|
+
module EmptyEye
|
2
|
+
class ShardCollection
|
3
|
+
|
4
|
+
#a collection of all the view_shards
|
5
|
+
#these are wranglers for the shards
|
6
|
+
#uses 'array' as a proxy
|
7
|
+
#performs array methods by passing things off in method missing
|
8
|
+
|
9
|
+
def initialize(primary_shard_klass)
|
10
|
+
@master_class = primary_shard_klass.master_class
|
11
|
+
@primary = PrimaryShard.new(primary_shard_klass)
|
12
|
+
@array = [@primary]
|
13
|
+
end
|
14
|
+
|
15
|
+
#the proxy object for instances
|
16
|
+
def array
|
17
|
+
@array
|
18
|
+
end
|
19
|
+
|
20
|
+
#we want to see the proxy object not the class info
|
21
|
+
def inspect
|
22
|
+
array.inspect
|
23
|
+
end
|
24
|
+
|
25
|
+
#the class to which the shards belongs
|
26
|
+
def master_class
|
27
|
+
@master_class
|
28
|
+
end
|
29
|
+
|
30
|
+
def descend(klass)
|
31
|
+
@master_class = klass
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
#add shard based on association from master_class
|
36
|
+
def create_with(assoc)
|
37
|
+
new_shard = Shard.new(assoc)
|
38
|
+
reject! {|shard| shard.name == new_shard.name}
|
39
|
+
push(new_shard)
|
40
|
+
new_shard
|
41
|
+
end
|
42
|
+
|
43
|
+
def schema_version
|
44
|
+
@schema_version
|
45
|
+
end
|
46
|
+
|
47
|
+
#takes the name of shard and a hash of intended updates from master instance
|
48
|
+
#returns a subset of hash with only values the shard handles
|
49
|
+
def delegate_map(name, hash)
|
50
|
+
keys = update_mapping[name] & hash.keys
|
51
|
+
keys.inject({}) do |res, col|
|
52
|
+
res[col] = hash[col] if hash[col]
|
53
|
+
res
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#the primary shard
|
58
|
+
def primary
|
59
|
+
@primary
|
60
|
+
end
|
61
|
+
|
62
|
+
#array of shard classes
|
63
|
+
def klasses
|
64
|
+
map(&:klass)
|
65
|
+
end
|
66
|
+
|
67
|
+
def names
|
68
|
+
map(&:name)
|
69
|
+
end
|
70
|
+
|
71
|
+
#this object responds to array methods
|
72
|
+
def respond_to?(m)
|
73
|
+
super || array.respond_to?(m)
|
74
|
+
end
|
75
|
+
|
76
|
+
#delegate to the array proxy when the method is missing
|
77
|
+
def method_missing(m, *args, &block)
|
78
|
+
if respond_to?(m)
|
79
|
+
array.send(m, *args, &block)
|
80
|
+
else
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def view_sql
|
86
|
+
@view_sql
|
87
|
+
end
|
88
|
+
|
89
|
+
#generates view sql
|
90
|
+
def create_view_sql
|
91
|
+
#determine what shard will handle what columns
|
92
|
+
map_attribute_management
|
93
|
+
#start with primary table
|
94
|
+
query = primary_arel_table
|
95
|
+
|
96
|
+
#build select clause with correct table handling the appropriate columns
|
97
|
+
query = query.project(*arel_columns)
|
98
|
+
|
99
|
+
#build joins
|
100
|
+
each do |shard|
|
101
|
+
next if shard.primary
|
102
|
+
current = shard.arel_table
|
103
|
+
key = shard.foreign_key.to_sym
|
104
|
+
if shard.type_column
|
105
|
+
query.join(current).on(
|
106
|
+
primary.key.eq(current[key]), shard.type_column.eq(shard.type_value)
|
107
|
+
)
|
108
|
+
else
|
109
|
+
query.join(current).on(
|
110
|
+
primary.key.eq(current[key])
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
self.schema_version = Digest::MD5.hexdigest(query.to_sql)
|
116
|
+
query.project("'#{schema_version}' AS mti_schema_version")
|
117
|
+
|
118
|
+
#we dont need to keep this data
|
119
|
+
free_arel_columns
|
120
|
+
|
121
|
+
#STI condition if needed
|
122
|
+
if primary.sti_also?
|
123
|
+
query.where(primary.type_column.eq(primary.type_value))
|
124
|
+
end
|
125
|
+
|
126
|
+
#build view creation statement
|
127
|
+
@view_sql = "CREATE VIEW #{primary.klass.compute_view_name} AS\n#{query.to_sql}"
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def schema_version=(md5_hash)
|
133
|
+
@schema_version = md5_hash
|
134
|
+
end
|
135
|
+
|
136
|
+
#all of the arel columns mapped to the right arel tables
|
137
|
+
def arel_columns
|
138
|
+
@arel_columns ||= []
|
139
|
+
end
|
140
|
+
|
141
|
+
#we dont need to keep this data
|
142
|
+
def free_arel_columns
|
143
|
+
@arel_columns = nil
|
144
|
+
end
|
145
|
+
|
146
|
+
#tracks the attributes with the shard that will handle it
|
147
|
+
def update_mapping
|
148
|
+
@update_mapping ||= {}
|
149
|
+
end
|
150
|
+
|
151
|
+
#generate a foreign_key if it is missing
|
152
|
+
def default_foreign_key
|
153
|
+
view_name = master_class.table_name.singularize
|
154
|
+
"#{view_name}_id"
|
155
|
+
end
|
156
|
+
|
157
|
+
#the primary arel table
|
158
|
+
def primary_arel_table
|
159
|
+
primary.arel_table
|
160
|
+
end
|
161
|
+
|
162
|
+
#all the tables
|
163
|
+
def tables
|
164
|
+
map(&:table)
|
165
|
+
end
|
166
|
+
|
167
|
+
#map the columns to the shard that will handle it
|
168
|
+
def map_attribute_management
|
169
|
+
#clear out what we know
|
170
|
+
arel_columns.clear
|
171
|
+
#use this to track and remove dupes
|
172
|
+
tracker = {}
|
173
|
+
each do |shard|
|
174
|
+
#mimic the master_class's associations through primary shard
|
175
|
+
primary.has_another(shard)
|
176
|
+
shard.columns.each do |col|
|
177
|
+
column = col.to_sym
|
178
|
+
#skip if we already have this column
|
179
|
+
next if tracker[column]
|
180
|
+
#set to true so we wont do again
|
181
|
+
tracker[column] = true
|
182
|
+
#add the column based on the shard's arel_table
|
183
|
+
arel_columns << shard.arel_table[column]
|
184
|
+
#later we need to know how to update thing correctly
|
185
|
+
update_mapping[shard.name] = update_mapping[shard.name].to_a << col
|
186
|
+
#delegate the setter for column to klass of shard through primary shard
|
187
|
+
primary.delegate_to(column, shard) unless shard.primary
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
module EmptyEye
|
2
|
+
module ShardWrangler
|
3
|
+
#module which extends the class that serves as a pointer to the primary table
|
4
|
+
#when there is a superclass the shard will inherit from that, else it will inherit from ActiveRecord
|
5
|
+
#the primary shard manages all the MTI associated tables for the master class
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
#this module method creates a ShardWrangler extended ActiveRecord inherited class
|
12
|
+
#the class will wrangle our shards
|
13
|
+
def self.create(master_class, t_name)
|
14
|
+
inherit_from = if master_class.base_class == master_class
|
15
|
+
ActiveRecord::Base
|
16
|
+
else
|
17
|
+
master_class.superclass
|
18
|
+
end
|
19
|
+
|
20
|
+
table_name = if t_name
|
21
|
+
t_name
|
22
|
+
elsif master_class.descends_from_active_record?
|
23
|
+
"#{master_class.name.underscore.pluralize}_core"
|
24
|
+
else
|
25
|
+
master_class.superclass.table_name
|
26
|
+
end
|
27
|
+
|
28
|
+
new_class = Class.new(inherit_from)
|
29
|
+
new_class.send(:include, ShardWrangler)
|
30
|
+
new_class.table_name = table_name
|
31
|
+
new_class.master_class = master_class
|
32
|
+
EmptyEye.const_set("#{master_class.to_s}Wrangler", new_class)
|
33
|
+
new_class
|
34
|
+
end
|
35
|
+
|
36
|
+
#the instance that owns this wrangler
|
37
|
+
#we usually know the master instance ahead of time
|
38
|
+
#so we should take care to set this manually
|
39
|
+
#we want to avoid the lookup
|
40
|
+
def mti_instance
|
41
|
+
@mti_instance || master_class.find_by_id(id)
|
42
|
+
end
|
43
|
+
|
44
|
+
#setter used to associate the wrangler with the master instance
|
45
|
+
def mti_instance=(instance)
|
46
|
+
@mti_instance = instance
|
47
|
+
end
|
48
|
+
|
49
|
+
#special save so that the wrangler can keep the master's instance tables consistent
|
50
|
+
def cascade_save
|
51
|
+
write_attributes
|
52
|
+
#this will autosave shards
|
53
|
+
save
|
54
|
+
#reset the id and then reload
|
55
|
+
mti_instance.id = id
|
56
|
+
mti_instance.reload
|
57
|
+
end
|
58
|
+
|
59
|
+
#reflection on master class; this should never change
|
60
|
+
def master_class
|
61
|
+
self.class.master_class
|
62
|
+
end
|
63
|
+
|
64
|
+
def valid?(context = nil)
|
65
|
+
context ||= (new_record? ? :create : :update)
|
66
|
+
write_attributes
|
67
|
+
output = super(context)
|
68
|
+
errors.each do |attr, message|
|
69
|
+
mti_instance.errors.add(attr, message)
|
70
|
+
end
|
71
|
+
errors.empty? && output
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def write_attributes
|
77
|
+
#make sure all the shards are there
|
78
|
+
cascade_build_associations
|
79
|
+
#this will propagate setters to the appropriate shards
|
80
|
+
assign_attributes(mti_safe_attributes)
|
81
|
+
self.type = master_class.name if respond_to?("type=")
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def shards
|
86
|
+
self.class.shards
|
87
|
+
end
|
88
|
+
|
89
|
+
#make sure the primary shard only tries to update what he should
|
90
|
+
def mti_safe_attributes
|
91
|
+
mti_instance.attributes.except(
|
92
|
+
*self.class.primary_shard.exclude
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
#all the instance shards should exist but lets be certain
|
97
|
+
#using an autobuild would be more efficient here
|
98
|
+
#we shouldnt load associations we dont need to
|
99
|
+
def cascade_build_associations
|
100
|
+
#go through each shard making sure it is exists and is loaded
|
101
|
+
shards.each do |shard|
|
102
|
+
next if shard.primary
|
103
|
+
assoc = send(shard.name)
|
104
|
+
assoc ||= send("build_#{shard.name}")
|
105
|
+
send("#{shard.name}=", assoc)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
module ClassMethods
|
110
|
+
|
111
|
+
#the wrangler uses special reflection; overriden here
|
112
|
+
def create_reflection(macro, name, options, active_record)
|
113
|
+
raise(EmptyEye::NotYetSupported, "through associations are not yet spported") if options[:through]
|
114
|
+
klass = options[:through] ? ShardThroughReflection : ShardAssociationReflection
|
115
|
+
reflection = klass.new(macro, name, options, active_record)
|
116
|
+
|
117
|
+
self.reflections = self.reflections.merge(name => reflection)
|
118
|
+
reflection
|
119
|
+
end
|
120
|
+
|
121
|
+
#finder methods should use the master class's type not the wrangler's
|
122
|
+
def type_condition(table = arel_table)
|
123
|
+
sti_column = table[inheritance_column.to_sym]
|
124
|
+
|
125
|
+
sti_column.eq(master_class.name)
|
126
|
+
end
|
127
|
+
|
128
|
+
#overriding find_by_id
|
129
|
+
#this is used to retrieve the wrangler instance for the master instance
|
130
|
+
#the type column is removed
|
131
|
+
def find_by_id(val)
|
132
|
+
query = columns_except_type
|
133
|
+
query = query.where(arel_table[:id].eq(val))
|
134
|
+
find_by_sql(query.to_sql).first
|
135
|
+
end
|
136
|
+
|
137
|
+
#the wrangler uses a special association builder
|
138
|
+
def has_one(name, options = {})
|
139
|
+
Associations::Builder::ShardHasOne.build(self, name, options)
|
140
|
+
end
|
141
|
+
|
142
|
+
#reflection on master class; this should never change
|
143
|
+
def master_class
|
144
|
+
@master_class
|
145
|
+
end
|
146
|
+
|
147
|
+
#the master_class value is set with this setter; should happen only once
|
148
|
+
def master_class=(klass)
|
149
|
+
@master_class = klass
|
150
|
+
end
|
151
|
+
|
152
|
+
#overriding to reset the special instance variable
|
153
|
+
def reset_column_information
|
154
|
+
@columns_except_type = nil
|
155
|
+
super
|
156
|
+
end
|
157
|
+
|
158
|
+
#the primary shard
|
159
|
+
def primary_shard
|
160
|
+
shards.primary
|
161
|
+
end
|
162
|
+
|
163
|
+
#we know the associations and we know what they can do
|
164
|
+
#we will make a mti class accordingly here
|
165
|
+
def wrangle_shards(mti_ancestors)
|
166
|
+
mti_ancestors.each do |assoc|
|
167
|
+
shards.create_with(assoc)
|
168
|
+
end
|
169
|
+
create_view if create_view?
|
170
|
+
master_class.reset_column_information
|
171
|
+
end
|
172
|
+
|
173
|
+
#batch deletion when there are conditions
|
174
|
+
#kill indiscriminately otherwise
|
175
|
+
def cascade_delete_all(conditions)
|
176
|
+
mti_clear_identity_map
|
177
|
+
affected = 0
|
178
|
+
ids = []
|
179
|
+
ids = conditions ? select(arel_table[primary_key.to_sym]).where(conditions).collect(&:id) : []
|
180
|
+
transaction do
|
181
|
+
begin
|
182
|
+
batch = ids.pop(10000)
|
183
|
+
shards.each do |shard|
|
184
|
+
result = if conditions.nil?
|
185
|
+
shard.klass.delete_all
|
186
|
+
elsif shard.polymorphic_type
|
187
|
+
shard.klass.delete_all(shard.foreign_key => batch, shard.polymorphic_type => shard.type_value)
|
188
|
+
else
|
189
|
+
shard.klass.delete_all(shard.foreign_key => batch)
|
190
|
+
end
|
191
|
+
affected = [affected, result].max
|
192
|
+
end
|
193
|
+
end until ids.to_a.empty?
|
194
|
+
end
|
195
|
+
affected
|
196
|
+
end
|
197
|
+
|
198
|
+
def cascade_update_all(updates, conditions, options)
|
199
|
+
mti_clear_identity_map
|
200
|
+
affected = 0
|
201
|
+
stringified_updates = updates.stringify_keys
|
202
|
+
ids = conditions ? select(arel_table[primary_key.to_sym]).where(conditions).apply_finder_options(options.slice(:limit, :order)).collect(&:id) : []
|
203
|
+
transaction do
|
204
|
+
begin
|
205
|
+
batch = ids.pop(10000)
|
206
|
+
shards.each do |shard|
|
207
|
+
cols = shards.delegate_map(shard.name, stringified_updates)
|
208
|
+
next if cols.empty?
|
209
|
+
result = if conditions.nil?
|
210
|
+
shard.klass.update_all(cols)
|
211
|
+
elsif shard.polymorphic_type
|
212
|
+
shard.klass.update_all(cols, shard.foreign_key => batch, shard.polymorphic_type => shard.type_value)
|
213
|
+
else
|
214
|
+
shard.klass.update_all(cols, shard.foreign_key => batch)
|
215
|
+
end
|
216
|
+
affected = [affected, result].max
|
217
|
+
end
|
218
|
+
end until ids.to_a.empty?
|
219
|
+
end
|
220
|
+
affected
|
221
|
+
end
|
222
|
+
|
223
|
+
def shards
|
224
|
+
@shards ||= EmptyEye::ShardCollection.new(self)
|
225
|
+
end
|
226
|
+
|
227
|
+
#we need a name for the view
|
228
|
+
#need to have a way to set this
|
229
|
+
def compute_view_name
|
230
|
+
if master_class.descends_from_active_record?
|
231
|
+
master_class.send(:compute_table_name)
|
232
|
+
else
|
233
|
+
master_class.name.underscore.pluralize
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
def mti_clear_identity_map
|
240
|
+
ActiveRecord::IdentityMap.repository[symbolized_base_class].clear if ActiveRecord::IdentityMap.enabled?
|
241
|
+
end
|
242
|
+
|
243
|
+
#get the schema version
|
244
|
+
#we shouldnt recreate views that we donth have to
|
245
|
+
def mti_schema_version
|
246
|
+
check_for_name_error
|
247
|
+
return nil unless connection.table_exists?(compute_view_name)
|
248
|
+
return nil unless mti_view_versioned?
|
249
|
+
t = Arel::Table.new(compute_view_name)
|
250
|
+
q = t.project(t[:mti_schema_version])
|
251
|
+
connection.select_value(q.to_sql)
|
252
|
+
rescue
|
253
|
+
nil
|
254
|
+
end
|
255
|
+
|
256
|
+
#determine if what we want to name our view already exists
|
257
|
+
def check_for_name_error
|
258
|
+
if connection.tables_without_views.include?(compute_view_name)
|
259
|
+
raise(EmptyEye::ViewNameError, "MTI view cannot be created because a table named '#{compute_view_name}' already exists")
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
#we need to create the sql first to determine the schema_version
|
264
|
+
#if the current schema version is the same as the old dont recreate the view
|
265
|
+
#if it is nil then recreate
|
266
|
+
def create_view?
|
267
|
+
shards.create_view_sql
|
268
|
+
schema_version = mti_schema_version
|
269
|
+
schema_version.nil? or schema_version != shards.schema_version
|
270
|
+
end
|
271
|
+
|
272
|
+
#always recreate
|
273
|
+
def mti_view_versioned?
|
274
|
+
connection.columns(compute_view_name).any? {|c| c.name == 'mti_schema_version'}
|
275
|
+
end
|
276
|
+
|
277
|
+
#drop the view; dont check if we can, just rescue any errors
|
278
|
+
#create the view
|
279
|
+
def create_view
|
280
|
+
connection.execute("DROP VIEW #{compute_view_name}") rescue nil
|
281
|
+
connection.execute(shards.view_sql)
|
282
|
+
end
|
283
|
+
|
284
|
+
#build the arel query once and memoize it
|
285
|
+
#this is essentially the select to remove type column
|
286
|
+
def columns_except_type
|
287
|
+
@columns_except_type ||= begin
|
288
|
+
query = arel_table
|
289
|
+
(column_names - [inheritance_column]).each do |c|
|
290
|
+
query = query.project(arel_table[c.to_sym])
|
291
|
+
end
|
292
|
+
query
|
293
|
+
end
|
294
|
+
@columns_except_type.dup
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
data/lib/empty_eye/version.rb
CHANGED
data/lib/empty_eye.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
require "active_record"
|
2
2
|
require "arel"
|
3
|
+
require 'digest'
|
3
4
|
|
4
5
|
require "empty_eye/version"
|
5
6
|
|
6
7
|
require "empty_eye/persistence"
|
7
8
|
require "empty_eye/relation"
|
8
9
|
require "empty_eye/errors"
|
9
|
-
require "empty_eye/view_extension"
|
10
|
-
require "empty_eye/primary_view_extension"
|
11
|
-
require "empty_eye/view_extension_collection"
|
12
10
|
require "empty_eye/shard"
|
11
|
+
require "empty_eye/primary_shard"
|
12
|
+
require "empty_eye/shard_collection"
|
13
|
+
require "empty_eye/shard_wrangler"
|
13
14
|
require "empty_eye/associations/builder/shard_has_one"
|
14
15
|
require "empty_eye/associations/shard_has_one_association"
|
15
16
|
require "empty_eye/associations/shard_association_scope"
|
@@ -20,7 +21,7 @@ require "empty_eye/active_record/schema_dumper"
|
|
20
21
|
require "empty_eye/active_record/connection_adapter"
|
21
22
|
|
22
23
|
module EmptyEye
|
23
|
-
|
24
|
+
|
24
25
|
end
|
25
26
|
|
26
27
|
::ActiveRecord::Base.send :include, EmptyEye::Persistence
|
data/spec/spec_helper.rb
CHANGED
@@ -50,12 +50,6 @@ ActiveRecord::Migration.create_table :eating_venues_core, :force => true do |t|
|
|
50
50
|
t.string :longitude
|
51
51
|
end
|
52
52
|
|
53
|
-
ActiveRecord::Migration.create_table :eating_venues_core, :force => true do |t|
|
54
|
-
t.string :api_venue_id
|
55
|
-
t.string :latitude
|
56
|
-
t.string :longitude
|
57
|
-
end
|
58
|
-
|
59
53
|
ActiveRecord::Migration.create_table :garages, :force => true do |t|
|
60
54
|
t.boolean :privately_owned
|
61
55
|
t.integer :max_wait_days
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: empty_eye
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 9
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 4
|
9
|
-
-
|
10
|
-
version: 0.4.
|
9
|
+
- 3
|
10
|
+
version: 0.4.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- thegboat
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-03-
|
18
|
+
date: 2012-03-12 00:00:00 -04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -104,13 +104,13 @@ files:
|
|
104
104
|
- lib/empty_eye/associations/shard_has_one_association.rb
|
105
105
|
- lib/empty_eye/errors.rb
|
106
106
|
- lib/empty_eye/persistence.rb
|
107
|
-
- lib/empty_eye/
|
107
|
+
- lib/empty_eye/primary_shard.rb
|
108
108
|
- lib/empty_eye/relation.rb
|
109
109
|
- lib/empty_eye/shard.rb
|
110
110
|
- lib/empty_eye/shard_association_reflection.rb
|
111
|
+
- lib/empty_eye/shard_collection.rb
|
112
|
+
- lib/empty_eye/shard_wrangler.rb
|
111
113
|
- lib/empty_eye/version.rb
|
112
|
-
- lib/empty_eye/view_extension.rb
|
113
|
-
- lib/empty_eye/view_extension_collection.rb
|
114
114
|
- spec/configuration_spec.rb
|
115
115
|
- spec/mti_crud_spec.rb
|
116
116
|
- spec/mti_to_sti_to_mti_crud_spec.rb
|
@@ -1,114 +0,0 @@
|
|
1
|
-
module EmptyEye
|
2
|
-
class ViewExtension
|
3
|
-
|
4
|
-
#extension for parent class
|
5
|
-
#tracks associations for database updates managed by primary extension
|
6
|
-
#has many of the same interfaces as primary view extension
|
7
|
-
|
8
|
-
def initialize(association)
|
9
|
-
@association = association
|
10
|
-
end
|
11
|
-
|
12
|
-
#exclude from view generation always
|
13
|
-
def self.exclude_always
|
14
|
-
['id','created_at','updated_at','deleted_at', 'type']
|
15
|
-
end
|
16
|
-
|
17
|
-
#association that this extension will build upon
|
18
|
-
def association
|
19
|
-
@association
|
20
|
-
end
|
21
|
-
|
22
|
-
#the table columns that will be extended in sql
|
23
|
-
def columns
|
24
|
-
restrictions - exclude
|
25
|
-
end
|
26
|
-
|
27
|
-
#never the primary
|
28
|
-
def primary
|
29
|
-
false
|
30
|
-
end
|
31
|
-
|
32
|
-
#table of the shard
|
33
|
-
def table
|
34
|
-
association.table_name
|
35
|
-
end
|
36
|
-
|
37
|
-
#name of the association
|
38
|
-
def name
|
39
|
-
association.name
|
40
|
-
end
|
41
|
-
|
42
|
-
#used to create view
|
43
|
-
def arel_table
|
44
|
-
@arel_table ||= begin
|
45
|
-
t= Arel::Table.new(table)
|
46
|
-
t.table_alias = alias_name if alias_name != table
|
47
|
-
t
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
#foreign key of the shard; used in view generation and database updates
|
52
|
-
def foreign_key
|
53
|
-
association.foreign_key
|
54
|
-
end
|
55
|
-
|
56
|
-
#the shard is simply the class of the association
|
57
|
-
def shard
|
58
|
-
association.klass
|
59
|
-
end
|
60
|
-
|
61
|
-
#arel column of polymorphic type field
|
62
|
-
def type_column
|
63
|
-
arel_table[polymorphic_type.to_sym] if polymorphic_type
|
64
|
-
end
|
65
|
-
|
66
|
-
#value of the polymorphic column
|
67
|
-
def type_value
|
68
|
-
parent.base_class.name if polymorphic_type
|
69
|
-
end
|
70
|
-
|
71
|
-
def polymorphic_type
|
72
|
-
return unless association.options[:as]
|
73
|
-
"#{association.options[:as]}_type"
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
#class to whom this extension belongs
|
79
|
-
def parent
|
80
|
-
association.active_record
|
81
|
-
end
|
82
|
-
|
83
|
-
#class of the extension table
|
84
|
-
def klass
|
85
|
-
association.klass
|
86
|
-
end
|
87
|
-
|
88
|
-
#uses association name to create alias to prevent non unique aliases
|
89
|
-
def alias_name
|
90
|
-
name.to_s.pluralize
|
91
|
-
end
|
92
|
-
|
93
|
-
#user declared exceptions ... exclude these columns from the parent inheritance
|
94
|
-
def exceptions
|
95
|
-
association.options[:except].to_a.collect(&:to_s)
|
96
|
-
end
|
97
|
-
|
98
|
-
#user declared restrictions ... restrict parent inheritance columns to these
|
99
|
-
def restrictions
|
100
|
-
only = association.options[:only].to_a.collect(&:to_s)
|
101
|
-
only.empty? ? table_columns : only
|
102
|
-
end
|
103
|
-
|
104
|
-
#we want to omit these columns
|
105
|
-
def exclude
|
106
|
-
[exceptions, self.class.exclude_always, foreign_key, polymorphic_type].flatten.uniq
|
107
|
-
end
|
108
|
-
|
109
|
-
#all the columns of the extensions table
|
110
|
-
def table_columns
|
111
|
-
klass.column_names
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|