Skip to content

Commit

Permalink
finished first version of the SqlLoader module, which loads the sql o…
Browse files Browse the repository at this point in the history
…bjects required for each object type like Friend or TeamMember, which uses views/rules. need to hook it into the application
  • Loading branch information
dcunited001 committed Aug 12, 2011
1 parent 3d530af commit c589142
Show file tree
Hide file tree
Showing 17 changed files with 251 additions and 37 deletions.
21 changes: 18 additions & 3 deletions README.md
Expand Up @@ -11,10 +11,25 @@ I've starting off by testing and creating the basic models necessary for the pro
---

### Up Next
* Finish up SqlLoader module for loading extraneous PostgreSQL scripts
* Finish up model tests for Team Requests relationship
* Set up acceptance testing
* Acceptance testing for guests and their limited access
* Acceptance testing for basic member features
* Acceptance testing for basic admin features
* The good stuff
* Rinse, Repeat

## Areas of Interest:
* Friends Relationship
* maintained with a PostgreSQL view
* the relationship defined in friend.rb allows someone to call Member.first.friends.first.friends.last... etc
* using PostgreSQL rules to allow for Member.first.friends.first.delete (`DELETE FROM view_friends`)
* the above friend would instead have their corresponding friendship record inactivated
* TeamRequest/TeamMember/TeamCaptain relationships
* designed along the same lines as the Member/Friend relationships
* SqlLoader module (db/sql_loader.rb and db/sql_loader.*)
* tied into the Migrations for unobtrusive declaration of SQL views and rules
* and spec_helper.rb to easily allow extra SQL objects to be created for tests

## Please Note
#### This project is PostgreSQL specific
* There are several views, rules, etc. that are defined inside (db/sql_loader.*)
* MySQL doesn't have rules to associate to views
3 changes: 0 additions & 3 deletions app/models/friend.rb
@@ -1,9 +1,6 @@
class Friend < Member
set_table_name 'v_friends'

#how to get your friend's friends ?
#has_many :friends, :foreign_key => 'member_id'

def friends
super #it's ssssuper, thanks for asking
end
Expand Down
5 changes: 5 additions & 0 deletions config/environment.rb
@@ -1,7 +1,12 @@
# Load the rails application
require File.expand_path('../application', __FILE__)
require File.expand_path('../../lib/devise_names', __FILE__)

# Load the Sinatra GuestApp
require File.expand_path('../../lib/guest/guest_app', __FILE__)

# Load the SqlLoader Module to help create extra SQL objects
require File.expand_path('../../db/sql_loader', __FILE__)

# Initialize the rails application
Sk8::Application.initialize!
2 changes: 1 addition & 1 deletion config/environments/test.rb
Expand Up @@ -32,7 +32,7 @@
# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
# config.active_record.schema_format = :sql_loader

# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
Expand Down
@@ -1,6 +1,6 @@
class CreateTeamMembers < ActiveRecord::Migration
class CreateTeamRequests < ActiveRecord::Migration
def self.up
create_table "team_members", :force => true do |t|
create_table "team_requests", :force => true do |t|
t.integer "team_id"
t.integer "member_requesting_id"
t.integer "member_requested_id"
Expand All @@ -17,12 +17,12 @@ def self.up

#TODO: create view to obtain a list of friends as an association

add_index :team_members, [:team_id], :name => "team_id_index"
add_index :team_members, [:member_requested_id], :name => "member_requested_id_index"
add_index :team_requests, [:team_id], :name => "team_id_index"
add_index :team_requests, [:member_requested_id], :name => "member_requested_id_index"
#add_index :team_members, [:member_requesting_id], :name => "member_requesting_id_index" #this index is probably going to be read from less
end

def self.down
drop_table :team_members
drop_table :team_requests
end
end
26 changes: 12 additions & 14 deletions db/migrate/20110711104341_create_friends_view.rb
Expand Up @@ -11,18 +11,21 @@ def self.up
JOIN members m2
ON ((f.member_requested_id = m2.id) OR (f.member_requesting_id = m2.id))
AND (m.id != m2.id)
WHERE f.active = true')
WHERE f.active = true;')

# This rule takes care of Member.first.friends.first.delete
# without the developer having to know anything about the sql implementation

# CREATE RULE v_friends_delete AS ON DELETE TO v_friends
# DO INSTEAD UPDATE friendships SET active = 0 WHERE id = OLD.friendship_id

# DELETE RULE
execute('
CREATE RULE v_friends_delete AS
ON DELETE TO v_friends DO INSTEAD
UPDATE friendships SET active = 0
WHERE id = OLD.friendship_id;
')


UPDATE friendships SET active = false
WHERE id = OLD.friendship_id;')

# Insert Rule for Friendships won't work at the moment,
# because Friends.<< is expecting a Friend and not a member
Expand All @@ -39,14 +42,9 @@ def self.up
#
#')


# This takes care of Member.first.friends.first.delete
# without the developer having to know anything about the sql implementation

# CREATE RULE v_friends_delete AS ON DELETE TO v_friends
# DO INSTEAD UPDATE friendships SET active = 0 WHERE id = OLD.friendship_id

# (need to add friendships.id to the view as well)
# INSERT EXAMPLE FROM LOG:
# [1mINSERT INTO "roles" ("created_at", "member_id", "name", "role_id", "rollable_id", "rollable_type", "updated_at") VALUES
# ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"ESC[0m [["created_at", Thu, 11 Aug 2011 22:40:25 UTC +00:00], ["member_id", 101], ["name", "appuser"], ["role_id", nil], ["rollable_id", nil], ["rollable_type", nil], ["updated_at", Thu, 11 Aug 2011 22:40:25 UTC +00:00]]


end
Expand Down
28 changes: 25 additions & 3 deletions db/migrate/20110711104358_create_team_member_view.rb
@@ -1,9 +1,31 @@
class CreateTeamMemberView < ActiveRecord::Migration
def self.up

create_table :team_member_views do |t|
t.string :pending
end
execute('
CREATE VIEW v_team_members AS
SELECT t.id as team_id, tm.id as team_request_id, this_member.id as member_id, team_mems.*
FROM members this_member
INNER JOIN team_requests active_tm
ON (this_member.id = active_tm.member_requesting_id
OR this_member.id = active_tm.member_requested_id)
AND active_tm.active = true
INNER JOIN teams t
ON active_tm.team_id = t.id
INNER JOIN team_requests tm
ON t.id = tm.team_id
AND tm.active = true
INNER JOIN members team_mems
ON (team_mems.id = tm.member_requesting_id and t.creator_id = this_member.id)
OR (team_mems.id = tm.member_requested_id and t.creator_id != this_member.id)
WHERE (this_member.id != team_mems.id);')

execute('
CREATE RULE v_team_members_delete AS
ON DELETE TO v_team_members DO INSTEAD
UPDATE team_requests SET active = false
WHERE id = OLD.team_request_id;')

#'SELECT team_mems.*
#FROM devise this_member
Expand Down
10 changes: 3 additions & 7 deletions db/schema.rb
Expand Up @@ -141,11 +141,7 @@
t.datetime "updated_at"
end

create_table "team_member_views", :force => true do |t|
t.string "pending"
end

create_table "team_members", :force => true do |t|
create_table "team_requests", :force => true do |t|
t.integer "team_id"
t.integer "member_requesting_id"
t.integer "member_requested_id"
Expand All @@ -160,8 +156,8 @@
t.datetime "updated_at"
end

add_index "team_members", ["member_requested_id"], :name => "member_requested_id_index"
add_index "team_members", ["team_id"], :name => "team_id_index"
add_index "team_requests", ["member_requested_id"], :name => "member_requested_id_index"
add_index "team_requests", ["team_id"], :name => "team_id_index"

create_table "teams", :force => true do |t|
t.string "name"
Expand Down
116 changes: 116 additions & 0 deletions db/sql_loader.rb
@@ -0,0 +1,116 @@
#this is way too fragile, but I'm not sure
# how to specify the order in which objects are created/dropped
# short of creating a configuration yaml file that
# specifies the dependencies between each object

#there's probably a million better ways to do this
# but it's my first time creating a module like this,
# sue me

module SqlLoader
class SqlLoaderBase
CREATE_START = 'Creating objects for'
CREATE_ERRORS = 'Encountered errors creating objects:'
CREATE_DELIMETER = '================================='
NO_CREATE_ERRORS = 'All objects created successfully!'

DROP_START = 'Dropping objects for'
DROP_ERRORS = 'Encountered errors dropping objects:'
DROP_DELIMETER = '================================='
NO_DROP_ERRORS = 'All objects dropped successfully!'

SQL_LOADER_ROOT = File.join(Rails.root, 'db/sql_loader')
Dir[File.join(SQL_LOADER_ROOT, "*.rb")].each {|rb| require rb }

def self.relative_name
name.to_s.split('::').last
end

def self.get_sql_files
#for each type, append it's sql files into an array in an order that can be executed
sql_object_types = %w[table view index rule function]

sql_object_types.each_with_object([]) do |type, files|
files.concat(Dir[File.join(SQL_LOADER_ROOT, relative_name.underscore, "#{type}_*.sql")])
end
end

def self.create
errors = []

puts "#{CREATE_START} #{relative_name}"

get_sql_files.each do |sql|
begin
ActiveRecord::Base.connection.execute(IO.read(sql))
rescue
errors << $!
end
end

create_error_output(errors)
end

def self.drop
errors = []

puts "#{DROP_START} #{relative_name}"

get_sql_files.reverse.each do |f|
sql = drop_statement(f)

begin
puts "============= " + sql
ActiveRecord::Base.connection.execute(sql)
rescue
errors << $!
end
end

drop_error_output(errors)
end

def self.drop_statement(filename)
case File.basename(filename, '.sql')
when /(^table.*)/i
return "DROP TABLE IF EXISTS#{$1};"
when /(^view.*)/i
return "DROP VIEW IF EXISTS #{$1};"
when /(^rule_(.*)_(insert|delete|update))/i
return "DROP RULE IF EXISTS #{$1} ON #{$2};"
end
end

# ERROR OUTPUT

def self.create_error_output errors
out = "Creating #{relative_name} Objects: "

if errors.any?
out += "#{CREATE_ERRORS}\n"
errors.each {|e| out += "\n\n#{CREATE_DELIMETER}\n#{e}\n" }

puts out
raise "SqlLoader Exception"
else
out += NO_CREATE_ERRORS
puts out
end
end

def self.drop_error_output errors
out = "Dropping #{relative_name} Objects: "

if errors.any?
out += "#{DROP_ERRORS}\n"
errors.each {|e| out += "\n\n#{DROP_DELIMETER}\n#{e}\n" }

puts out
raise "SqlLoader Exception"
else
out += NO_DROP_ERRORS
puts out
end
end
end
end
5 changes: 5 additions & 0 deletions db/sql_loader/friend.rb
@@ -0,0 +1,5 @@
module SqlLoader
class Friend < SqlLoaderBase

end
end
5 changes: 5 additions & 0 deletions db/sql_loader/friend/rule_view_friends_delete.sql
@@ -0,0 +1,5 @@
CREATE RULE rule_view_friends_delete AS
ON DELETE TO view_friends DO INSTEAD

UPDATE friendships SET active = false
WHERE id = OLD.friendship_id;
10 changes: 10 additions & 0 deletions db/sql_loader/friend/view_friends.sql
@@ -0,0 +1,10 @@
CREATE VIEW view_friends AS

SELECT m.id as member_id, f.id as friendship_id, m2.*
FROM members m
JOIN friendships f
ON (m.id = f.member_requested_id) OR (m.id = f.member_requesting_id)
JOIN members m2
ON ((f.member_requested_id = m2.id) OR (f.member_requesting_id = m2.id))
AND (m.id != m2.id)
WHERE f.active = true;
5 changes: 5 additions & 0 deletions db/sql_loader/team_captain.rb
@@ -0,0 +1,5 @@
module SqlLoader
class TeamCaptain < SqlLoaderBase

end
end
5 changes: 5 additions & 0 deletions db/sql_loader/team_member.rb
@@ -0,0 +1,5 @@
module SqlLoader
class TeamMember < SqlLoaderBase

end
end
5 changes: 5 additions & 0 deletions db/sql_loader/team_member/rule_view_team_members_delete.sql
@@ -0,0 +1,5 @@
CREATE RULE rule_view_team_members_delete AS
ON DELETE TO view_team_members DO INSTEAD

UPDATE team_requests SET active = false
WHERE id = OLD.team_request_id;
18 changes: 18 additions & 0 deletions db/sql_loader/team_member/view_team_members.sql
@@ -0,0 +1,18 @@

CREATE VIEW view_team_members AS

SELECT t.id as team_id, tm.id as team_request_id, this_member.id as member_id, team_mems.*
FROM members this_member
INNER JOIN team_requests active_tm
ON (this_member.id = active_tm.member_requesting_id
OR this_member.id = active_tm.member_requested_id)
AND active_tm.active = true
INNER JOIN teams t
ON active_tm.team_id = t.id
INNER JOIN team_requests tm
ON t.id = tm.team_id
AND tm.active = true
INNER JOIN members team_mems
ON (team_mems.id = tm.member_requesting_id and t.creator_id = this_member.id)
OR (team_mems.id = tm.member_requested_id and t.creator_id != this_member.id)
WHERE (this_member.id != team_mems.id);
14 changes: 13 additions & 1 deletion spec/models/member_spec.rb
Expand Up @@ -156,7 +156,19 @@
subject.friends.should_not include(@member_sent_request_to_subject)
end

it 'can tell when friends are equal devise' do
it 'can delete friends from its list' do
subject.friends.should include(@friend_sent_by_subject)
subject.friends.should include(@friend_sent_to_subject)
subject.friends.should_not include(@not_friends_anymore)

deleted_friend = subject.friends.first
subject.friends.should include(deleted_friend)

deleted.friend.delete
subject.friends.should_not include(deleted_friend)
end

it 'can tell when friends are equal' do
pending
end

Expand Down

0 comments on commit c589142

Please sign in to comment.