empty_eye 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|