From 91a8b4ab9cd6c601e98d23e9e316049feea6cbbd Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 20 Jul 2015 11:01:08 -0500 Subject: [PATCH 01/67] VRFS-3316 : Reviews/Ratings * schemas for reviews and review_summaries * migrations * models * validations * initial specs to verify creation of reviews and review_summaries with a jamtrack and subsequently, various validations. --- db/manifest | 3 +- db/up/reviews.sql | 23 ++++++ ruby/lib/jam_ruby.rb | 2 + ruby/lib/jam_ruby/models/review.rb | 13 ++++ ruby/lib/jam_ruby/models/review_summary.rb | 12 +++ ruby/lib/jam_ruby/models/user.rb | 3 + ruby/spec/jam_ruby/models/review_spec.rb | 86 ++++++++++++++++++++++ 7 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 db/up/reviews.sql create mode 100644 ruby/lib/jam_ruby/models/review.rb create mode 100644 ruby/lib/jam_ruby/models/review_summary.rb create mode 100644 ruby/spec/jam_ruby/models/review_spec.rb diff --git a/db/manifest b/db/manifest index bd6ca8ce6..d0f27852b 100755 --- a/db/manifest +++ b/db/manifest @@ -296,4 +296,5 @@ add_description_to_perf_samples.sql alter_genre_player_unique_constraint.sql musician_search.sql enhance_band_profile.sql -alter_band_profile_rate_defaults.sql \ No newline at end of file +alter_band_profile_rate_defaults.sql +reviews.sql \ No newline at end of file diff --git a/db/up/reviews.sql b/db/up/reviews.sql new file mode 100644 index 000000000..50ae9df5f --- /dev/null +++ b/db/up/reviews.sql @@ -0,0 +1,23 @@ +CREATE TABLE reviews ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE, + target_id VARCHAR(64) NOT NULL, + target_type VARCHAR(32) NOT NULL, + description VARCHAR(8000), + rating INT NOT NULL, + deleted_by_user_id VARCHAR(64) REFERENCES users(id) ON DELETE SET NULL, + deleted_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL +); + +CREATE TABLE review_summaries ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + target_id VARCHAR(64) NOT NULL, + target_type VARCHAR(32) NOT NULL, + avg_rating FLOAT NOT NULL, + wilson_score FLOAT NOT NULL, + review_count INT NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL +); \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 82484997e..1069aa184 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -111,6 +111,8 @@ require "jam_ruby/models/machine_fingerprint" require "jam_ruby/models/machine_extra" require "jam_ruby/models/fraud_alert" require "jam_ruby/models/fingerprint_whitelist" +require "jam_ruby/models/review" +require "jam_ruby/models/review_summary" require "jam_ruby/models/rsvp_request" require "jam_ruby/models/rsvp_slot" require "jam_ruby/models/rsvp_request_rsvp_slot" diff --git a/ruby/lib/jam_ruby/models/review.rb b/ruby/lib/jam_ruby/models/review.rb new file mode 100644 index 000000000..454e03943 --- /dev/null +++ b/ruby/lib/jam_ruby/models/review.rb @@ -0,0 +1,13 @@ +module JamRuby + class Review < ActiveRecord::Base + attr_accessible :target, :rating, :description, :user + belongs_to :target, polymorphic: true + belongs_to :user, foreign_key: 'user_id', class_name: "JamRuby::User" + belongs_to :deleted_by_user, foreign_key: 'deleted_by_user_id', class_name: "JamRuby::User" + + validates :rating, presence:true, numericality: {only_integer: true, minimum:1, maximum:5} + validates :target, presence:true + validates :user, presence:true + + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/review_summary.rb b/ruby/lib/jam_ruby/models/review_summary.rb new file mode 100644 index 000000000..77e25d0e7 --- /dev/null +++ b/ruby/lib/jam_ruby/models/review_summary.rb @@ -0,0 +1,12 @@ +module JamRuby + class ReviewSummary < ActiveRecord::Base + attr_accessible :target, :target_type, :avg_rating, :wilson_score, :review_count + belongs_to :target, polymorphic: true + belongs_to :user, foreign_key: 'user_id', class_name: "JamRuby::User" + + validates :avg_rating, presence:true, numericality: true + validates :review_count, presence:true, numericality: {only_integer: true} + validates :wilson_score, presence:true, numericality: {greater_than:0, less_than:1} + validates :target, presence:true + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 217c73339..5e4e2ad22 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -45,6 +45,9 @@ module JamRuby # authorizations (for facebook, etc -- omniauth) has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization" + has_many :reviews, :class_name => "JamRuby::Review" + has_many :review_summaries, :class_name => "JamRuby::ReviewSummary" + # calendars (for scheduling NOT in music_session) has_many :calendars, :class_name => "JamRuby::Calendar" diff --git a/ruby/spec/jam_ruby/models/review_spec.rb b/ruby/spec/jam_ruby/models/review_spec.rb new file mode 100644 index 000000000..b8dd67f4d --- /dev/null +++ b/ruby/spec/jam_ruby/models/review_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe Review do + + shared_examples_for :review do |target, target_type| + before(:each) do + Review.delete_all + User.delete_all + @user = FactoryGirl.create(:user) + end + + after(:all) do + Review.delete_all + User.delete_all + end + + context "validates review" do + it "blank target" do + review = Review.create() + review.valid?.should be_false + review.errors[:target].should == ["can't be blank"] + end + + it "no rating" do + review = Review.create(target:target) + review.valid?.should be_false + review.errors[:rating].should include("can't be blank") + review.errors[:rating].should include("is not a number") + end + + it "no user" do + review = Review.create(target:target, rating:3) + review.valid?.should be_false + review.errors[:user].should include("can't be blank") + end + + it "complete" do + review = Review.create(target:target, rating:3, user:@user) + review.valid?.should be_true + end + end + + context "validates review summary" do + it "blank target" do + review_summary = ReviewSummary.create() + review_summary.valid?.should be_false + review_summary.errors[:target].should == ["can't be blank"] + end + + it "no rating" do + review_summary = ReviewSummary.create(target:target) + review_summary.valid?.should be_false + review_summary.errors[:target].should be_empty + review_summary.errors[:avg_rating].should include("can't be blank") + review_summary.errors[:avg_rating].should include("is not a number") + end + + it "no score" do + review_summary = ReviewSummary.create(target:target, avg_rating:3.2) + review_summary.valid?.should be_false + review_summary.errors[:target].should be_empty + review_summary.errors[:avg_rating].should be_empty + review_summary.errors[:wilson_score].should include("can't be blank") + review_summary.errors[:wilson_score].should include("is not a number") + end + + it "no count" do + review_summary = ReviewSummary.create(target:target, avg_rating:3.2, wilson_score:0.95) + review_summary.valid?.should be_false + review_summary.errors[:review_count].should include("can't be blank") + end + + it "complete" do + review_summary = ReviewSummary.create(target:target, avg_rating:3.2, wilson_score:0.95, review_count: 15) + puts "complete:: #{review_summary.errors.inspect}" + review_summary.valid?.should be_true + end + end + end + + describe "with a jamtrack" do + @jam_track = FactoryGirl.create(:jam_track) + it_behaves_like :review, @jam_track, "jam_track" + end + +end \ No newline at end of file From 0beb386beadcc366159a964564218fa20243ff97 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 20 Jul 2015 11:29:19 -0500 Subject: [PATCH 02/67] VRFS-3316 : Review Uniqueness validation and spec. --- ruby/lib/jam_ruby/models/review.rb | 7 +++++++ ruby/spec/jam_ruby/models/review_spec.rb | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ruby/lib/jam_ruby/models/review.rb b/ruby/lib/jam_ruby/models/review.rb index 454e03943..bdddb6d73 100644 --- a/ruby/lib/jam_ruby/models/review.rb +++ b/ruby/lib/jam_ruby/models/review.rb @@ -6,8 +6,15 @@ module JamRuby belongs_to :deleted_by_user, foreign_key: 'deleted_by_user_id', class_name: "JamRuby::User" validates :rating, presence:true, numericality: {only_integer: true, minimum:1, maximum:5} + validates :target, presence:true validates :user, presence:true + validates :target_id, uniqueness: {scope: :user_id, message: "There is already a review for this User and Target."} + # # @options - can contain values: + # # * target_id (optional) + # def reduce(options) + # arel = Review.where("deleted_at=?", nil) + # end end end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/review_spec.rb b/ruby/spec/jam_ruby/models/review_spec.rb index b8dd67f4d..622b688aa 100644 --- a/ruby/spec/jam_ruby/models/review_spec.rb +++ b/ruby/spec/jam_ruby/models/review_spec.rb @@ -35,9 +35,17 @@ describe Review do end it "complete" do - review = Review.create(target:target, rating:3, user:@user) + review = Review.create(target:target, rating:3, user:@user) review.valid?.should be_true end + + it "unique" do + review = Review.create(target:target, rating:3, user:@user) + review.valid?.should be_true + + review2 = Review.create(target:target, rating:3, user:@user) + review2.valid?.should be_false + end end context "validates review summary" do From 4b52d97a64cc3d8ff8acf34bea153d367eeef041 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 20 Jul 2015 11:35:16 -0500 Subject: [PATCH 03/67] VRFS-3316 : Review summary uniqueness validation and spec. --- ruby/lib/jam_ruby/models/review_summary.rb | 4 ++-- ruby/spec/jam_ruby/models/review_spec.rb | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ruby/lib/jam_ruby/models/review_summary.rb b/ruby/lib/jam_ruby/models/review_summary.rb index 77e25d0e7..6e0ef6b11 100644 --- a/ruby/lib/jam_ruby/models/review_summary.rb +++ b/ruby/lib/jam_ruby/models/review_summary.rb @@ -2,11 +2,11 @@ module JamRuby class ReviewSummary < ActiveRecord::Base attr_accessible :target, :target_type, :avg_rating, :wilson_score, :review_count belongs_to :target, polymorphic: true - belongs_to :user, foreign_key: 'user_id', class_name: "JamRuby::User" - + validates :avg_rating, presence:true, numericality: true validates :review_count, presence:true, numericality: {only_integer: true} validates :wilson_score, presence:true, numericality: {greater_than:0, less_than:1} validates :target, presence:true + validates :target_id, uniqueness:true end end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/review_spec.rb b/ruby/spec/jam_ruby/models/review_spec.rb index 622b688aa..e9fe640f9 100644 --- a/ruby/spec/jam_ruby/models/review_spec.rb +++ b/ruby/spec/jam_ruby/models/review_spec.rb @@ -83,6 +83,14 @@ describe Review do puts "complete:: #{review_summary.errors.inspect}" review_summary.valid?.should be_true end + + it "unique" do + review = ReviewSummary.create(target:target, avg_rating:3, wilson_score:0.82, review_count:14) + review.valid?.should be_true + + review2 = ReviewSummary.create(target:target, avg_rating:3.22, wilson_score:0.91, review_count:12) + review2.valid?.should be_false + end end end From 2c594bf7834b41332adf2d91df31cf86919c9843 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 20 Jul 2015 16:49:22 -0500 Subject: [PATCH 04/67] VRFS-3316 : Reduce method to roll up reviews * Creates review_summaries * Calculate and store wilson score * Unit tests --- ruby/lib/jam_ruby/models/review.rb | 36 ++++++++++++++++++---- ruby/lib/jam_ruby/models/review_summary.rb | 7 +++-- ruby/spec/factories.rb | 11 +++++++ ruby/spec/jam_ruby/models/review_spec.rb | 28 ++++++++++++++++- 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/ruby/lib/jam_ruby/models/review.rb b/ruby/lib/jam_ruby/models/review.rb index bdddb6d73..4e7726acc 100644 --- a/ruby/lib/jam_ruby/models/review.rb +++ b/ruby/lib/jam_ruby/models/review.rb @@ -10,11 +10,35 @@ module JamRuby validates :target, presence:true validates :user, presence:true validates :target_id, uniqueness: {scope: :user_id, message: "There is already a review for this User and Target."} - - # # @options - can contain values: - # # * target_id (optional) - # def reduce(options) - # arel = Review.where("deleted_at=?", nil) - # end + + class << self + # Create review_summary records by grouping reviews + def reduce() + ReviewSummary.transaction do + ReviewSummary.destroy_all + Review.select("target_id, target_type AS target_type, AVG(rating) as avg_rating, count(*) as review_count, SUM(CASE WHEN rating>=3.0 THEN 1 ELSE 0 END) AS pos_count").group("target_type, target_id") + .each do |r| + #puts "Reducing reviews: #{r.inspect} #{r.pos_count} / #{r.review_count}" + ReviewSummary.create!( + target_id: r.target_id, + target_type: r.target_type, + avg_rating: r.avg_rating, + wilson_score: ci_lower_bound(r.pos_count, r.review_count), + review_count: r.review_count + ) + end # each + end # transaction + end # reduce + + def ci_lower_bound(pos, n, confidence=0.95) + pos=pos.to_f + n=n.to_f + return 0 if n == 0 + z = 1.96 # Statistics2.pnormaldist(1-(1-confidence)/2) + phat = 1.0*pos/n + (phat + z*z/(2*n) - z * Math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n) + end + + end # self end end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/review_summary.rb b/ruby/lib/jam_ruby/models/review_summary.rb index 6e0ef6b11..94a21e901 100644 --- a/ruby/lib/jam_ruby/models/review_summary.rb +++ b/ruby/lib/jam_ruby/models/review_summary.rb @@ -1,12 +1,13 @@ module JamRuby class ReviewSummary < ActiveRecord::Base - attr_accessible :target, :target_type, :avg_rating, :wilson_score, :review_count + attr_accessible :target, :target_id, :target_type, :avg_rating, :wilson_score, :review_count belongs_to :target, polymorphic: true validates :avg_rating, presence:true, numericality: true validates :review_count, presence:true, numericality: {only_integer: true} validates :wilson_score, presence:true, numericality: {greater_than:0, less_than:1} - validates :target, presence:true - validates :target_id, uniqueness:true + validates :target_id, presence:true, uniqueness:true + + end end \ No newline at end of file diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 90cd48c44..893a09c17 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -139,6 +139,17 @@ FactoryGirl.define do end end + factory :review, :class => JamRuby::Review do + sequence(:name) { |n| "Band" } + biography "My Biography" + city "Apex" + state "NC" + country "US" + before(:create) { |review| + review.genres << Genre.first + } + end + factory :music_session, :class => JamRuby::MusicSession do sequence(:name) { |n| "Music Session #{n}" } sequence(:description) { |n| "Music Session Description #{n}" } diff --git a/ruby/spec/jam_ruby/models/review_spec.rb b/ruby/spec/jam_ruby/models/review_spec.rb index e9fe640f9..dcdced01b 100644 --- a/ruby/spec/jam_ruby/models/review_spec.rb +++ b/ruby/spec/jam_ruby/models/review_spec.rb @@ -46,8 +46,33 @@ describe Review do review2 = Review.create(target:target, rating:3, user:@user) review2.valid?.should be_false end - end + it "reduces" do + review = Review.create(target:target, rating:3, user:@user) + review.valid?.should be_true + + review2 = Review.create(target:target, rating:5, user:FactoryGirl.create(:user)) + review2.valid?.should be_true + Review.count.should eq(2) + ReviewSummary.count.should eq(0) + Review.reduce() + ReviewSummary.count.should eq(1) + ReviewSummary.first.avg_rating.should eq(4.0) + + puts "ORIG: #{ReviewSummary.all.inspect}" + ws_orig = ReviewSummary.first.wilson_score + avg_orig = ReviewSummary.first.avg_rating + + 5.times {Review.create(target:target, rating:5, user:FactoryGirl.create(:user))} + Review.reduce() + + ReviewSummary.first.wilson_score.should > ws_orig + ReviewSummary.first.avg_rating.should > avg_orig + + puts "ALL: #{ReviewSummary.all.inspect}" + end + end # context + context "validates review summary" do it "blank target" do review_summary = ReviewSummary.create() @@ -99,4 +124,5 @@ describe Review do it_behaves_like :review, @jam_track, "jam_track" end + end \ No newline at end of file From 74b8c81a8afabf6ea2df56a9872014299791502d Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 20 Jul 2015 19:11:52 -0500 Subject: [PATCH 05/67] VRFS-3316 : Review query method and spec to verify. --- ruby/lib/jam_ruby/models/review.rb | 2 +- ruby/lib/jam_ruby/models/review_summary.rb | 29 ++++++++++++ ruby/spec/jam_ruby/models/review_spec.rb | 53 +++++++++++++++++++++- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/ruby/lib/jam_ruby/models/review.rb b/ruby/lib/jam_ruby/models/review.rb index 4e7726acc..d7e6c07d1 100644 --- a/ruby/lib/jam_ruby/models/review.rb +++ b/ruby/lib/jam_ruby/models/review.rb @@ -12,7 +12,7 @@ module JamRuby validates :target_id, uniqueness: {scope: :user_id, message: "There is already a review for this User and Target."} class << self - # Create review_summary records by grouping reviews + # Create review_summary records by grouping reviews def reduce() ReviewSummary.transaction do ReviewSummary.destroy_all diff --git a/ruby/lib/jam_ruby/models/review_summary.rb b/ruby/lib/jam_ruby/models/review_summary.rb index 94a21e901..f66e3aacd 100644 --- a/ruby/lib/jam_ruby/models/review_summary.rb +++ b/ruby/lib/jam_ruby/models/review_summary.rb @@ -8,6 +8,35 @@ module JamRuby validates :wilson_score, presence:true, numericality: {greater_than:0, less_than:1} validates :target_id, presence:true, uniqueness:true + class << self + # Query review_summaries using target type, id, and minimum review count + # * target_type: Only return review summaries for given target type + # * target_id: Only return review summary for given target type + # * minimum_reviews: Only return review summary made up of at least this many reviews + # * arel: start with pre-queried reviews (arel object) + # sorts by wilson score + def index(options={}) + if (options.key?(:arel)) + arel = options[:arel].order("wilson_score DESC") + else + arel = ReviewSummary.order("wilson_score DESC") + end + + if (options.key?(:target_type)) + arel = arel.where("target_type=?", options[:target_type]) + end + + if (options.key?(:target_id)) + arel = arel.where("target_id=?", options[:target_id]) + end + + if (options.key?(:minimum_reviews)) + arel = arel.where("review_count>=?", options[:minimum_reviews]) + end + + arel + end + end end end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/review_spec.rb b/ruby/spec/jam_ruby/models/review_spec.rb index dcdced01b..656babb49 100644 --- a/ruby/spec/jam_ruby/models/review_spec.rb +++ b/ruby/spec/jam_ruby/models/review_spec.rb @@ -54,6 +54,8 @@ describe Review do review2 = Review.create(target:target, rating:5, user:FactoryGirl.create(:user)) review2.valid?.should be_true Review.count.should eq(2) + + # Reduce and check: ReviewSummary.count.should eq(0) Review.reduce() ReviewSummary.count.should eq(1) @@ -63,6 +65,7 @@ describe Review do ws_orig = ReviewSummary.first.wilson_score avg_orig = ReviewSummary.first.avg_rating + # Create some more and verify: 5.times {Review.create(target:target, rating:5, user:FactoryGirl.create(:user))} Review.reduce() @@ -72,12 +75,12 @@ describe Review do puts "ALL: #{ReviewSummary.all.inspect}" end end # context - + context "validates review summary" do it "blank target" do review_summary = ReviewSummary.create() review_summary.valid?.should be_false - review_summary.errors[:target].should == ["can't be blank"] + review_summary.errors[:target_id].should == ["can't be blank"] end it "no rating" do @@ -116,6 +119,52 @@ describe Review do review2 = ReviewSummary.create(target:target, avg_rating:3.22, wilson_score:0.91, review_count:12) review2.valid?.should be_false end + + it "reduces and queries" do + review = Review.create(target:target, rating:3, user:@user) + review.valid?.should be_true + review2 = Review.create(target:target, rating:5, user:FactoryGirl.create(:user)) + review2.valid?.should be_true + Review.count.should eq(2) + + # Reduce and check: + ReviewSummary.count.should eq(0) + Review.reduce() + ReviewSummary.count.should eq(1) + ReviewSummary.first.avg_rating.should eq(4.0) + + puts "ORIG: #{ReviewSummary.all.inspect}" + ws_orig = ReviewSummary.first.wilson_score + avg_orig = ReviewSummary.first.avg_rating + + + # Create some more and verify: + 5.times {Review.create(target:target, rating:5, user:FactoryGirl.create(:user))} + Review.reduce() + ReviewSummary.count.should eq(1) + ReviewSummary.first.wilson_score.should > ws_orig + ReviewSummary.first.avg_rating.should > avg_orig + + + # Create some more with a different target and verify: + target2=FactoryGirl.create(:jam_track) + 5.times {Review.create(target:target2, rating:5, user:FactoryGirl.create(:user))} + Review.reduce() + summaries = ReviewSummary.index() + summaries.count.should eq(2) + summaries[0].wilson_score.should > summaries[1].wilson_score + + summaries = ReviewSummary.index(target_id: target2) + puts "MULTIPLE TARGET ALL: #{summaries.inspect}" + summaries.count.should eq(1) + summaries[0].target_id.should eq(target2.id) + + summaries = ReviewSummary.index(target_type: "JamRuby::JamTrack") + summaries.count.should eq(2) + + summaries = ReviewSummary.index(minimum_reviews: 6) + summaries.count.should eq(1) + end end end From bd4b3380c26efabcd7019761fba8fc8b39bbd5f9 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 20 Jul 2015 20:33:20 -0500 Subject: [PATCH 06/67] VRFS-3316 : Review query method and spec to verify. --- ruby/lib/jam_ruby/models/review.rb | 40 ++++++++++++++++++------ ruby/spec/jam_ruby/models/review_spec.rb | 27 +++++++++------- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/ruby/lib/jam_ruby/models/review.rb b/ruby/lib/jam_ruby/models/review.rb index d7e6c07d1..41b589004 100644 --- a/ruby/lib/jam_ruby/models/review.rb +++ b/ruby/lib/jam_ruby/models/review.rb @@ -12,20 +12,40 @@ module JamRuby validates :target_id, uniqueness: {scope: :user_id, message: "There is already a review for this User and Target."} class << self + def index(options={}) + if(options.key?(:include_deleted)) + arel = Review.select("*") + else + arel = Review.where("deleted_at IS NULL") + end + + if(options.key?(:target_id)) + arel = arel.where("target_id=?", options[:target_id]) + end + + if(options.key?(:user_id)) + arel = arel.where("user_id=?", options[:user_id]) + end + + arel + end + # Create review_summary records by grouping reviews def reduce() ReviewSummary.transaction do ReviewSummary.destroy_all - Review.select("target_id, target_type AS target_type, AVG(rating) as avg_rating, count(*) as review_count, SUM(CASE WHEN rating>=3.0 THEN 1 ELSE 0 END) AS pos_count").group("target_type, target_id") - .each do |r| - #puts "Reducing reviews: #{r.inspect} #{r.pos_count} / #{r.review_count}" - ReviewSummary.create!( - target_id: r.target_id, - target_type: r.target_type, - avg_rating: r.avg_rating, - wilson_score: ci_lower_bound(r.pos_count, r.review_count), - review_count: r.review_count - ) + Review.select("target_id, target_type AS target_type, AVG(rating) as avg_rating, count(*) as review_count, SUM(CASE WHEN rating>=3.0 THEN 1 ELSE 0 END) AS pos_count") + .where("deleted_at IS NULL") + .group("target_type, target_id") + .each do |r| + wilson_score=ci_lower_bound(r.pos_count, r.review_count) + ReviewSummary.create!( + target_id: r.target_id, + target_type: r.target_type, + avg_rating: r.avg_rating, + wilson_score: wilson_score, + review_count: r.review_count + ) end # each end # transaction end # reduce diff --git a/ruby/spec/jam_ruby/models/review_spec.rb b/ruby/spec/jam_ruby/models/review_spec.rb index 656babb49..ace9d2dc1 100644 --- a/ruby/spec/jam_ruby/models/review_spec.rb +++ b/ruby/spec/jam_ruby/models/review_spec.rb @@ -53,12 +53,13 @@ describe Review do review2 = Review.create(target:target, rating:5, user:FactoryGirl.create(:user)) review2.valid?.should be_true - Review.count.should eq(2) + Review.should have(2).items + Review.index.should have(2).items # Reduce and check: - ReviewSummary.count.should eq(0) + ReviewSummary.should have(0).items Review.reduce() - ReviewSummary.count.should eq(1) + ReviewSummary.should have(1).items ReviewSummary.first.avg_rating.should eq(4.0) puts "ORIG: #{ReviewSummary.all.inspect}" @@ -67,6 +68,8 @@ describe Review do # Create some more and verify: 5.times {Review.create(target:target, rating:5, user:FactoryGirl.create(:user))} + Review.index.should have(7).items + Review.reduce() ReviewSummary.first.wilson_score.should > ws_orig @@ -125,12 +128,12 @@ describe Review do review.valid?.should be_true review2 = Review.create(target:target, rating:5, user:FactoryGirl.create(:user)) review2.valid?.should be_true - Review.count.should eq(2) + Review.should have(2).items # Reduce and check: - ReviewSummary.count.should eq(0) + ReviewSummary.should have(0).items Review.reduce() - ReviewSummary.count.should eq(1) + ReviewSummary.should have(1).items ReviewSummary.first.avg_rating.should eq(4.0) puts "ORIG: #{ReviewSummary.all.inspect}" @@ -141,7 +144,7 @@ describe Review do # Create some more and verify: 5.times {Review.create(target:target, rating:5, user:FactoryGirl.create(:user))} Review.reduce() - ReviewSummary.count.should eq(1) + ReviewSummary.should have(1).items ReviewSummary.first.wilson_score.should > ws_orig ReviewSummary.first.avg_rating.should > avg_orig @@ -149,21 +152,23 @@ describe Review do # Create some more with a different target and verify: target2=FactoryGirl.create(:jam_track) 5.times {Review.create(target:target2, rating:5, user:FactoryGirl.create(:user))} + Review.index.should have(12).items + Review.index(target_id: target2).should have(5).items Review.reduce() summaries = ReviewSummary.index() - summaries.count.should eq(2) + summaries.should have(2).items summaries[0].wilson_score.should > summaries[1].wilson_score summaries = ReviewSummary.index(target_id: target2) puts "MULTIPLE TARGET ALL: #{summaries.inspect}" - summaries.count.should eq(1) + summaries.should have(1).items summaries[0].target_id.should eq(target2.id) summaries = ReviewSummary.index(target_type: "JamRuby::JamTrack") - summaries.count.should eq(2) + summaries.should have(2).items summaries = ReviewSummary.index(minimum_reviews: 6) - summaries.count.should eq(1) + summaries.should have(1).items end end end From 1262dbfcc6847e024f855ac57c0581a2972b00f4 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 21 Jul 2015 14:47:35 -0500 Subject: [PATCH 07/67] VRFS-3316 : Reviews : routes and api controller * Expose review query methods as an API/rest interface. * Required updates to model * Spec to verify --- ruby/lib/jam_ruby/models/review.rb | 4 +- ruby/lib/jam_ruby/models/review_summary.rb | 1 + ruby/spec/factories.rb | 13 +- ruby/spec/jam_ruby/models/review_spec.rb | 2 +- web/app/controllers/api_reviews_controller.rb | 63 +++++++++ web/config/routes.rb | 6 + .../api_reviews_controller_spec.rb | 122 ++++++++++++++++++ 7 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 web/app/controllers/api_reviews_controller.rb create mode 100644 web/spec/controllers/api_reviews_controller_spec.rb diff --git a/ruby/lib/jam_ruby/models/review.rb b/ruby/lib/jam_ruby/models/review.rb index 41b589004..4c1031b3d 100644 --- a/ruby/lib/jam_ruby/models/review.rb +++ b/ruby/lib/jam_ruby/models/review.rb @@ -1,6 +1,6 @@ module JamRuby class Review < ActiveRecord::Base - attr_accessible :target, :rating, :description, :user + attr_accessible :target, :rating, :description, :user, :user_id, :target_id, :target_type belongs_to :target, polymorphic: true belongs_to :user, foreign_key: 'user_id', class_name: "JamRuby::User" belongs_to :deleted_by_user, foreign_key: 'deleted_by_user_id', class_name: "JamRuby::User" @@ -8,7 +8,7 @@ module JamRuby validates :rating, presence:true, numericality: {only_integer: true, minimum:1, maximum:5} validates :target, presence:true - validates :user, presence:true + validates :user_id, presence:true validates :target_id, uniqueness: {scope: :user_id, message: "There is already a review for this User and Target."} class << self diff --git a/ruby/lib/jam_ruby/models/review_summary.rb b/ruby/lib/jam_ruby/models/review_summary.rb index f66e3aacd..fb3833f5d 100644 --- a/ruby/lib/jam_ruby/models/review_summary.rb +++ b/ruby/lib/jam_ruby/models/review_summary.rb @@ -17,6 +17,7 @@ module JamRuby # * arel: start with pre-queried reviews (arel object) # sorts by wilson score def index(options={}) + options ||= {} if (options.key?(:arel)) arel = options[:arel].order("wilson_score DESC") else diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 893a09c17..bf45bdc20 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -138,18 +138,7 @@ FactoryGirl.define do end end end - - factory :review, :class => JamRuby::Review do - sequence(:name) { |n| "Band" } - biography "My Biography" - city "Apex" - state "NC" - country "US" - before(:create) { |review| - review.genres << Genre.first - } - end - + factory :music_session, :class => JamRuby::MusicSession do sequence(:name) { |n| "Music Session #{n}" } sequence(:description) { |n| "Music Session Description #{n}" } diff --git a/ruby/spec/jam_ruby/models/review_spec.rb b/ruby/spec/jam_ruby/models/review_spec.rb index ace9d2dc1..2b62bba89 100644 --- a/ruby/spec/jam_ruby/models/review_spec.rb +++ b/ruby/spec/jam_ruby/models/review_spec.rb @@ -31,7 +31,7 @@ describe Review do it "no user" do review = Review.create(target:target, rating:3) review.valid?.should be_false - review.errors[:user].should include("can't be blank") + review.errors[:user_id].should include("can't be blank") end it "complete" do diff --git a/web/app/controllers/api_reviews_controller.rb b/web/app/controllers/api_reviews_controller.rb new file mode 100644 index 000000000..83f8fed45 --- /dev/null +++ b/web/app/controllers/api_reviews_controller.rb @@ -0,0 +1,63 @@ +require 'sanitize' +class ApiReviewsController < ApiController + + before_filter :api_signed_in_user, :except => [:index] + #before_filter :auth_user, :only => [:create, :update, :delete] + before_filter :lookup_review_summary, :only => [:details] + before_filter :lookup_review, :only => [:update, :delete, :show] + + respond_to :json + + def index + summaries = ReviewSummary.index(params[:review]) + @reviews = summaries.paginate(page: params[:page], per_page: params[:per_page]) + respond_with @reviews, responder: ApiResponder, :status => 200 + end + + # Create a review: + def create + @review = Review.new + @review.target_id = params[:target_id] + @review.user = current_user + @review.rating = params[:rating] + @review.description = params[:description] + @review.target_type = params[:target_type] + @review.save + respond_with_model(@review) + end + + def details + reviews = Review.index(:target_id=>@review_summary.target_id) + @reviews = reviews.paginate(page: params[:page], per_page: params[:per_page]) + respond_with @reviews, responder: ApiResponder, :status => 200 + end + + def update + mods = params[:mods] + if mods.present? + @review.rating = mods[:rating] if mods.key?(:rating) + @review.description = mods[:description] if mods.key?(:description) + @review.save + end + respond_with_model(@review) + end + + def delete + @review.deleted_at = Time.now() + @review.save + render :json => {}, status: 204 + end + +private + + def lookup_review_summary + @review_summary = ReviewSummary.find(params[:review_summary_id]) + end + + def lookup_review + arel = Review.where("id=?", params[:id]) + arel = arel.where("user_id=?", current_user) unless current_user.admin + @review = arel.first + puts "FFFound: #{@review}" + end +end diff --git a/web/config/routes.rb b/web/config/routes.rb index 2281f6937..34711060e 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -288,6 +288,12 @@ SampleApp::Application.routes.draw do match '/users/complete/:signup_token' => 'api_users#complete', as: 'complete', via: 'post' match '/users/:id/set_password' => 'api_users#set_password', :via => :post + match '/reviews' => 'api_reviews#index', :via => :get + match '/reviews' => 'api_reviews#create', :via => :post + match '/reviews/:id' => 'api_reviews#update', :via => :post + match '/reviews/:id' => 'api_reviews#delete', :via => :delete + match '/reviews/details/:review_summary_id' => 'api_users#details', :via => :get, :as => 'api_summary_reviews' + # recurly match '/recurly/create_account' => 'api_recurly#create_account', :via => :post match '/recurly/delete_account' => 'api_recurly#delete_account', :via => :delete diff --git a/web/spec/controllers/api_reviews_controller_spec.rb b/web/spec/controllers/api_reviews_controller_spec.rb new file mode 100644 index 000000000..d129a45fb --- /dev/null +++ b/web/spec/controllers/api_reviews_controller_spec.rb @@ -0,0 +1,122 @@ +require 'spec_helper' + +describe ApiReviewsController do + render_views + + + before(:all) do + @logged_in_user = FactoryGirl.create(:user) + end + + before(:each) do + Review.destroy_all + ReviewSummary.destroy_all + @user= FactoryGirl.create(:user) + @target= FactoryGirl.create(:jam_track) + controller.current_user = @logged_in_user + end + + after(:all) do + Review.destroy_all + ReviewSummary.destroy_all + User.destroy_all + JamTrack.destroy_all + end + + describe "create" do + it "successful" do + post :create, rating:3, description:"it was ok", target_id: @target.id, target_type:"JamRuby::JamTrack", format: 'json' + response.should be_success + Review.index.should have(1).items + end + end + + describe "update" do + before :each do + @review=Review.create!(target:@target, rating:5, description: "blah", user_id: @logged_in_user.id) + end + it "basic" do + post :update, id:@review.id, mods: {rating:4, description: "not blah"}, :format=>'json' + response.should be_success + @review.reload + @review.rating.should eq(4) + @review.description.should eq("not blah") + end + end + + describe "delete" do + before :each do + @review=Review.create!(target:@target, rating:5, description: "blah", user_id: @logged_in_user.id) + end + it "marks review as deleted" do + delete :delete, id:@review.id + response.should be_success + Review.index.should have(0).items + Review.index(include_deleted:true).should have(1).items + end + end + + describe "indexes" do + before :each do + @target2=FactoryGirl.create(:jam_track) + + 7.times { Review.create!(target:@target, rating:4, description: "blah", user_id: FactoryGirl.create(:user).id) } + 5.times { Review.create!(target:@target2, rating:4, description: "blah", user_id: FactoryGirl.create(:user).id) } + end + + it "review summaries" do + get :index, format: 'json' + response.should be_success + json = JSON.parse(response.body) + json.should have(0).items + json = JSON.parse(response.body) + puts "response.inspect: #{JSON.pretty_generate(json)}" + + ReviewSummary.index.should have(0).items + Review.reduce() + ReviewSummary.index.should have(2).items + get :index, format: 'json' + json = JSON.parse(response.body) + json.should have(2).items + puts "response.inspect: #{JSON.pretty_generate(json)}" + + end + + it "details" do + ReviewSummary.index.should have(0).items + Review.reduce() + ReviewSummary.index.should have(2).items + + + summaries = ReviewSummary.index + get :details, :review_summary_id=>summaries[0].id, format: 'json' + response.should be_success + json = JSON.parse(response.body) + json.should have(7).items + + get :details, :review_summary_id=>summaries[1].id, format: 'json' + response.should be_success + json = JSON.parse(response.body) + json.should have(5).items + + puts "details: #{JSON.pretty_generate(json)}" + end + + it "paginates details" do + ReviewSummary.index.should have(0).items + Review.reduce() + summaries = ReviewSummary.index + summaries.should have(2).items + + get :details, review_summary_id:summaries[0].id, page: 1, per_page: 3, format: 'json' + response.should be_success + json = JSON.parse(response.body) + json.should have(3).items + + get :details, review_summary_id:summaries[0].id, page: 3, per_page: 3, format: 'json' + response.should be_success + json = JSON.parse(response.body) + json.should have(1).items + end + end +end From 2d4ce4cf3c24ed7fcc18e5ffe4e612a0dc9a1cff Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 21 Jul 2015 16:55:40 -0500 Subject: [PATCH 08/67] VRFS-3316 : Review cleanup, new tests --- web/app/controllers/api_reviews_controller.rb | 9 ++++++--- .../api_reviews_controller_spec.rb | 20 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/web/app/controllers/api_reviews_controller.rb b/web/app/controllers/api_reviews_controller.rb index 83f8fed45..a7ace92af 100644 --- a/web/app/controllers/api_reviews_controller.rb +++ b/web/app/controllers/api_reviews_controller.rb @@ -1,13 +1,12 @@ require 'sanitize' class ApiReviewsController < ApiController - before_filter :api_signed_in_user, :except => [:index] - #before_filter :auth_user, :only => [:create, :update, :delete] before_filter :lookup_review_summary, :only => [:details] before_filter :lookup_review, :only => [:update, :delete, :show] respond_to :json + # List review summaries according to params: def index summaries = ReviewSummary.index(params[:review]) @reviews = summaries.paginate(page: params[:page], per_page: params[:per_page]) @@ -26,12 +25,14 @@ class ApiReviewsController < ApiController respond_with_model(@review) end + # List reviews matching targets for given review summary: def details reviews = Review.index(:target_id=>@review_summary.target_id) @reviews = reviews.paginate(page: params[:page], per_page: params[:per_page]) respond_with @reviews, responder: ApiResponder, :status => 200 end + # Update a review: def update mods = params[:mods] if mods.present? @@ -42,8 +43,10 @@ class ApiReviewsController < ApiController respond_with_model(@review) end + # Mark a review as deleted: def delete @review.deleted_at = Time.now() + @review @review.save render :json => {}, status: 204 end @@ -58,6 +61,6 @@ private arel = Review.where("id=?", params[:id]) arel = arel.where("user_id=?", current_user) unless current_user.admin @review = arel.first - puts "FFFound: #{@review}" + raise ActiveRecord::RecordNotFound, "Couldn't find review matching #{arel}" if @review.nil? end end diff --git a/web/spec/controllers/api_reviews_controller_spec.rb b/web/spec/controllers/api_reviews_controller_spec.rb index d129a45fb..29a670c28 100644 --- a/web/spec/controllers/api_reviews_controller_spec.rb +++ b/web/spec/controllers/api_reviews_controller_spec.rb @@ -2,8 +2,6 @@ require 'spec_helper' describe ApiReviewsController do render_views - - before(:all) do @logged_in_user = FactoryGirl.create(:user) end @@ -35,6 +33,7 @@ describe ApiReviewsController do before :each do @review=Review.create!(target:@target, rating:5, description: "blah", user_id: @logged_in_user.id) end + it "basic" do post :update, id:@review.id, mods: {rating:4, description: "not blah"}, :format=>'json' response.should be_success @@ -42,12 +41,18 @@ describe ApiReviewsController do @review.rating.should eq(4) @review.description.should eq("not blah") end + + it "bad identifier" do + post :update, id:2112, mods: {rating:4, description: "not blah"}, :format=>'json' + response.status.should eql(404) + end end describe "delete" do before :each do @review=Review.create!(target:@target, rating:5, description: "blah", user_id: @logged_in_user.id) end + it "marks review as deleted" do delete :delete, id:@review.id response.should be_success @@ -68,18 +73,14 @@ describe ApiReviewsController do get :index, format: 'json' response.should be_success json = JSON.parse(response.body) - json.should have(0).items - json = JSON.parse(response.body) - puts "response.inspect: #{JSON.pretty_generate(json)}" + json.should have(0).items ReviewSummary.index.should have(0).items Review.reduce() ReviewSummary.index.should have(2).items get :index, format: 'json' json = JSON.parse(response.body) - json.should have(2).items - puts "response.inspect: #{JSON.pretty_generate(json)}" - + json.should have(2).item end it "details" do @@ -87,7 +88,6 @@ describe ApiReviewsController do Review.reduce() ReviewSummary.index.should have(2).items - summaries = ReviewSummary.index get :details, :review_summary_id=>summaries[0].id, format: 'json' response.should be_success @@ -98,8 +98,6 @@ describe ApiReviewsController do response.should be_success json = JSON.parse(response.body) json.should have(5).items - - puts "details: #{JSON.pretty_generate(json)}" end it "paginates details" do From 46cd9c47988afdf46f8f7b91294bd60c81a6c560 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 21 Jul 2015 18:10:45 -0500 Subject: [PATCH 09/67] Remove stray debug output. --- ruby/spec/jam_ruby/models/review_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ruby/spec/jam_ruby/models/review_spec.rb b/ruby/spec/jam_ruby/models/review_spec.rb index 2b62bba89..a8cc13cc7 100644 --- a/ruby/spec/jam_ruby/models/review_spec.rb +++ b/ruby/spec/jam_ruby/models/review_spec.rb @@ -62,7 +62,6 @@ describe Review do ReviewSummary.should have(1).items ReviewSummary.first.avg_rating.should eq(4.0) - puts "ORIG: #{ReviewSummary.all.inspect}" ws_orig = ReviewSummary.first.wilson_score avg_orig = ReviewSummary.first.avg_rating @@ -75,7 +74,6 @@ describe Review do ReviewSummary.first.wilson_score.should > ws_orig ReviewSummary.first.avg_rating.should > avg_orig - puts "ALL: #{ReviewSummary.all.inspect}" end end # context @@ -111,7 +109,6 @@ describe Review do it "complete" do review_summary = ReviewSummary.create(target:target, avg_rating:3.2, wilson_score:0.95, review_count: 15) - puts "complete:: #{review_summary.errors.inspect}" review_summary.valid?.should be_true end @@ -136,7 +133,6 @@ describe Review do ReviewSummary.should have(1).items ReviewSummary.first.avg_rating.should eq(4.0) - puts "ORIG: #{ReviewSummary.all.inspect}" ws_orig = ReviewSummary.first.wilson_score avg_orig = ReviewSummary.first.avg_rating @@ -160,7 +156,6 @@ describe Review do summaries[0].wilson_score.should > summaries[1].wilson_score summaries = ReviewSummary.index(target_id: target2) - puts "MULTIPLE TARGET ALL: #{summaries.inspect}" summaries.should have(1).items summaries[0].target_id.should eq(target2.id) From 82aa6bb24439dbeb44e9b15e92d3f9c46f2d6a7f Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 21 Jul 2015 19:14:18 -0500 Subject: [PATCH 10/67] Use scopes to clean up. --- ruby/lib/jam_ruby/models/review.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ruby/lib/jam_ruby/models/review.rb b/ruby/lib/jam_ruby/models/review.rb index 4c1031b3d..cbd0648d3 100644 --- a/ruby/lib/jam_ruby/models/review.rb +++ b/ruby/lib/jam_ruby/models/review.rb @@ -5,6 +5,9 @@ module JamRuby belongs_to :user, foreign_key: 'user_id', class_name: "JamRuby::User" belongs_to :deleted_by_user, foreign_key: 'deleted_by_user_id', class_name: "JamRuby::User" + scope :available, -> { where("deleted_at iS NULL") } + scope :all, -> { select("*")} + validates :rating, presence:true, numericality: {only_integer: true, minimum:1, maximum:5} validates :target, presence:true @@ -14,9 +17,9 @@ module JamRuby class << self def index(options={}) if(options.key?(:include_deleted)) - arel = Review.select("*") + arel = Review.all else - arel = Review.where("deleted_at IS NULL") + arel = Review.available end if(options.key?(:target_id)) From 82dd27754c0e07d0d78f3d4de94e366f3a8b0ab9 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 22 Jul 2015 02:54:10 +0000 Subject: [PATCH 11/67] Tweak --- web/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/README.md b/web/README.md index 393628cf1..f2dccf0a1 100644 --- a/web/README.md +++ b/web/README.md @@ -2,5 +2,3 @@ Jasmine Javascript Unit Tests ============================= Open browser to localhost:3000/teaspoon - - From cb3137ff8024ddb0fd3af63b1e3d6cf0feb9bfd9 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 24 Jul 2015 18:32:29 -0500 Subject: [PATCH 12/67] VRFS-3359 : Teacher Profile Migrations --- db/manifest | 1 + db/up/profile_teacher.sql | 73 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 db/up/profile_teacher.sql diff --git a/db/manifest b/db/manifest index 691ae0001..1c648aa07 100755 --- a/db/manifest +++ b/db/manifest @@ -298,3 +298,4 @@ musician_search.sql enhance_band_profile.sql alter_band_profile_rate_defaults.sql repair_band_profile.sql +profile_teacher.sql diff --git a/db/up/profile_teacher.sql b/db/up/profile_teacher.sql new file mode 100644 index 000000000..b2fed066e --- /dev/null +++ b/db/up/profile_teacher.sql @@ -0,0 +1,73 @@ +CREATE TABLE teachers ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, + introductory_video VARCHAR(1024) NULL, + years_teaching SMALLINT NOT NULL, + years_playing SMALLINT NOT NULL, + teaches_age_lower SMALLINT NOT NULL DEFAULT 0, + teaches_age_upper SMALLINT NOT NULL DEFAULT 0, + website VARCHAR(1024) NULL, + biography VARCHAR(4096) NOT NULL, + teaches_beginner BOOLEAN NOT NULL DEFAULT FALSE, + teaches_intermediat BOOLEAN NOT NULL DEFAULT FALSE, + teaches_advanced BOOLEAN NOT NULL DEFAULT FALSE, + price_per_lesson BOOLEAN NOT NULL DEFAULT FALSE, + price_per_month BOOLEAN NOT NULL DEFAULT FALSE, + lesson_duration_30 BOOLEAN NOT NULL DEFAULT FALSE, + lesson_duration_45 BOOLEAN NOT NULL DEFAULT FALSE, + lesson_duration_60 BOOLEAN NOT NULL DEFAULT FALSE, + lesson_duration_90 BOOLEAN NOT NULL DEFAULT FALSE, + lesson_duration_120 BOOLEAN NOT NULL DEFAULT FALSE, + price_per_lesson_cents INT NULL, + price_per_month_cents INT NULL, + lesson_duration_30_cents INT NULL, + lesson_duration_45_cents INT NULL, + lesson_duration_60_cents INT NULL, + lesson_duration_90_cents INT NULL, + lesson_duration_120_cents INT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE subjects( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + name VARCHAR(1024) NOT NULL, + description VARCHAR(4000) NULL +); + +CREATE TABLE languages( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + name VARCHAR(1024) NOT NULL, + description VARCHAR(4000) NULL +); + +-- Has many: +CREATE TABLE teacher_experience( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + teacher_id VARCHAR(64) REFERENCES teachers(id) ON DELETE CASCADE, + -- experience type: teaching, education, award: + experience_type VARCHAR(32) NOT NULL, + name VARCHAR(200) NOT NULL, + organization VARCHAR(200) NOT NULL, + start_year SMALLINT NOT NULL DEFAULT 0, + end_year SMALLINT NOT NULL DEFAULT 0 +); + +-- Has many/through tables: +CREATE TABLE teachers_genres( + teacher_id VARCHAR(64) REFERENCES teachers(id) ON DELETE CASCADE, + genre_id VARCHAR(64) REFERENCES genres(id) ON DELETE CASCADE +); +CREATE TABLE teachers_instruments( + teacher_id VARCHAR(64) REFERENCES teachers(id) ON DELETE CASCADE, + instrument_id VARCHAR(64) REFERENCES instruments(id) ON DELETE CASCADE +); +CREATE TABLE teachers_subjects( + teacher_id VARCHAR(64) REFERENCES teachers(id) ON DELETE CASCADE, + subject_id VARCHAR(64) REFERENCES subjects(id) ON DELETE CASCADE +); +CREATE TABLE teacher_languages( + teacher_id VARCHAR(64) REFERENCES teachers(id) ON DELETE CASCADE, + language_id VARCHAR(64) REFERENCES languages(id) ON DELETE CASCADE +); + From 460783a5aae6f985e995174e74b25520de55f65a Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 27 Jul 2015 10:02:37 -0500 Subject: [PATCH 13/67] VRFS-3359 : Teacher models, relationships, some validations, and initial tests. --- db/up/profile_teacher.sql | 42 +++++++++---------- ruby/lib/jam_ruby.rb | 4 ++ ruby/lib/jam_ruby/models/genre.rb | 3 ++ ruby/lib/jam_ruby/models/instrument.rb | 3 ++ ruby/lib/jam_ruby/models/language.rb | 7 ++++ ruby/lib/jam_ruby/models/subject.rb | 7 ++++ ruby/lib/jam_ruby/models/teacher.rb | 38 +++++++++++++++++ .../lib/jam_ruby/models/teacher_experience.rb | 7 ++++ ruby/spec/jam_ruby/models/teacher_spec.rb | 37 ++++++++++++++++ 9 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 ruby/lib/jam_ruby/models/language.rb create mode 100644 ruby/lib/jam_ruby/models/subject.rb create mode 100644 ruby/lib/jam_ruby/models/teacher.rb create mode 100644 ruby/lib/jam_ruby/models/teacher_experience.rb create mode 100644 ruby/spec/jam_ruby/models/teacher_spec.rb diff --git a/db/up/profile_teacher.sql b/db/up/profile_teacher.sql index b2fed066e..b8e0134a3 100644 --- a/db/up/profile_teacher.sql +++ b/db/up/profile_teacher.sql @@ -1,23 +1,23 @@ CREATE TABLE teachers ( id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, - introductory_video VARCHAR(1024) NULL, - years_teaching SMALLINT NOT NULL, - years_playing SMALLINT NOT NULL, - teaches_age_lower SMALLINT NOT NULL DEFAULT 0, - teaches_age_upper SMALLINT NOT NULL DEFAULT 0, - website VARCHAR(1024) NULL, - biography VARCHAR(4096) NOT NULL, - teaches_beginner BOOLEAN NOT NULL DEFAULT FALSE, - teaches_intermediat BOOLEAN NOT NULL DEFAULT FALSE, - teaches_advanced BOOLEAN NOT NULL DEFAULT FALSE, - price_per_lesson BOOLEAN NOT NULL DEFAULT FALSE, - price_per_month BOOLEAN NOT NULL DEFAULT FALSE, - lesson_duration_30 BOOLEAN NOT NULL DEFAULT FALSE, - lesson_duration_45 BOOLEAN NOT NULL DEFAULT FALSE, - lesson_duration_60 BOOLEAN NOT NULL DEFAULT FALSE, - lesson_duration_90 BOOLEAN NOT NULL DEFAULT FALSE, - lesson_duration_120 BOOLEAN NOT NULL DEFAULT FALSE, + user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, + introductory_video VARCHAR(1024) NULL, + years_teaching SMALLINT NOT NULL DEFAULT 0, + years_playing SMALLINT NOT NULL DEFAULT 0, + teaches_age_lower SMALLINT NOT NULL DEFAULT 0, + teaches_age_upper SMALLINT NOT NULL DEFAULT 0, + website VARCHAR(1024) NULL, + biography VARCHAR(4096) NULL, + teaches_beginner BOOLEAN NOT NULL DEFAULT FALSE, + teaches_intermediate BOOLEAN NOT NULL DEFAULT FALSE, + teaches_advanced BOOLEAN NOT NULL DEFAULT FALSE, + price_per_lesson BOOLEAN NOT NULL DEFAULT FALSE, + price_per_month BOOLEAN NOT NULL DEFAULT FALSE, + lesson_duration_30 BOOLEAN NOT NULL DEFAULT FALSE, + lesson_duration_45 BOOLEAN NOT NULL DEFAULT FALSE, + lesson_duration_60 BOOLEAN NOT NULL DEFAULT FALSE, + lesson_duration_90 BOOLEAN NOT NULL DEFAULT FALSE, + lesson_duration_120 BOOLEAN NOT NULL DEFAULT FALSE, price_per_lesson_cents INT NULL, price_per_month_cents INT NULL, lesson_duration_30_cents INT NULL, @@ -32,17 +32,17 @@ CREATE TABLE teachers ( CREATE TABLE subjects( id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(1024) NOT NULL, - description VARCHAR(4000) NULL + description VARCHAR(1024) NULL ); CREATE TABLE languages( id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(1024) NOT NULL, - description VARCHAR(4000) NULL + description VARCHAR(1024) NULL ); -- Has many: -CREATE TABLE teacher_experience( +CREATE TABLE teacher_experiences( id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), teacher_id VARCHAR(64) REFERENCES teachers(id) ON DELETE CASCADE, -- experience type: teaching, education, award: diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 82484997e..16ae29942 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -238,6 +238,10 @@ require "jam_ruby/models/performance_sample" require "jam_ruby/models/online_presence" require "jam_ruby/models/json_store" require "jam_ruby/models/musician_search" +require "jam_ruby/models/teacher" +require "jam_ruby/models/teacher_experience" +require "jam_ruby/models/language" +require "jam_ruby/models/subject" include Jampb diff --git a/ruby/lib/jam_ruby/models/genre.rb b/ruby/lib/jam_ruby/models/genre.rb index 1b0cd9ada..8ae195ffb 100644 --- a/ruby/lib/jam_ruby/models/genre.rb +++ b/ruby/lib/jam_ruby/models/genre.rb @@ -15,6 +15,9 @@ module JamRuby # genres has_and_belongs_to_many :recordings, :class_name => "JamRuby::Recording", :join_table => "recordings_genres" + # teachers + has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_genres" + # jam tracks has_many :jam_tracks, :class_name => "JamRuby::JamTrack" diff --git a/ruby/lib/jam_ruby/models/instrument.rb b/ruby/lib/jam_ruby/models/instrument.rb index 1a3fa8df7..cc6fe40a9 100644 --- a/ruby/lib/jam_ruby/models/instrument.rb +++ b/ruby/lib/jam_ruby/models/instrument.rb @@ -43,6 +43,9 @@ module JamRuby # music sessions has_and_belongs_to_many :music_sessions, :class_name => "JamRuby::ActiveMusicSession", :join_table => "genres_music_sessions" + # teachers + has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_instruments" + def self.standard_list return Instrument.where('instruments.popularity > 0').order('instruments.popularity DESC, instruments.description ASC') end diff --git a/ruby/lib/jam_ruby/models/language.rb b/ruby/lib/jam_ruby/models/language.rb new file mode 100644 index 000000000..bb5d64316 --- /dev/null +++ b/ruby/lib/jam_ruby/models/language.rb @@ -0,0 +1,7 @@ +module JamRuby + class Language < ActiveRecord::Base + include HtmlSanitize + html_sanitize strict: [:name, :description] + has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_languages" + end +end diff --git a/ruby/lib/jam_ruby/models/subject.rb b/ruby/lib/jam_ruby/models/subject.rb new file mode 100644 index 000000000..f20d75626 --- /dev/null +++ b/ruby/lib/jam_ruby/models/subject.rb @@ -0,0 +1,7 @@ +module JamRuby + class Subject < ActiveRecord::Base + include HtmlSanitize + html_sanitize strict: [:name, :description] + has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_subjects" + end +end diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb new file mode 100644 index 000000000..431635cbd --- /dev/null +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -0,0 +1,38 @@ +module JamRuby + class Teacher < ActiveRecord::Base + include HtmlSanitize + html_sanitize strict: [:biography, :website] + has_and_belongs_to_many :genres, :class_name => "JamRuby::Genre", :join_table => "teachers_genres" + has_and_belongs_to_many :instruments, :class_name => "JamRuby::Instrument", :join_table => "teachers_instruments" + has_and_belongs_to_many :subjects, :class_name => "JamRuby::Subject", :join_table => "teachers_subjects" + has_and_belongs_to_many :languages, :class_name => "JamRuby::Language", :join_table => "teachers_languages" + has_many :teacher_experiences, :class_name => "JamRuby::TeacherExperience" + belongs_to :user, :class_name => 'JamRuby::User' + + validates :user, :presence => true + + class << self + def save_teacher(user, params) + teacher = build_teacher(user, params) + teacher.save + teacher + end + + def build_teacher(user, params) + id = params[:id] + + # ensure person creating this Teacher is a Musician + unless user && user.musician? + raise JamPermissionError, "must be a musician" + end + + teacher = (id.blank?) ? Teacher.new : Teacher.find(id) + teacher.user = user + teacher.website = params[:website] if params.key?(:website) + teacher.biography = params[:biography] if params.key?(:biography) + teacher.introductory_video = params[:introductory_video] if params.key?(:introductory_video) + teacher + end + end + end +end diff --git a/ruby/lib/jam_ruby/models/teacher_experience.rb b/ruby/lib/jam_ruby/models/teacher_experience.rb new file mode 100644 index 000000000..181a62c08 --- /dev/null +++ b/ruby/lib/jam_ruby/models/teacher_experience.rb @@ -0,0 +1,7 @@ +module JamRuby + class TeacherExperience < ActiveRecord::Base + include HtmlSanitize + html_sanitize strict: [:name, :organization] + belongs_to :teacher, :class_name => "JamRuby::Teacher" + end +end diff --git a/ruby/spec/jam_ruby/models/teacher_spec.rb b/ruby/spec/jam_ruby/models/teacher_spec.rb new file mode 100644 index 000000000..f6eca828a --- /dev/null +++ b/ruby/spec/jam_ruby/models/teacher_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Teacher do + + let(:user) { FactoryGirl.create(:user) } + let(:user2) { FactoryGirl.create(:user) } + let(:fan) { FactoryGirl.create(:fan) } + BIO = "Once a man learned a guitar." + describe "can create" do + it "a simple teacher" do + teacher = Teacher.new + teacher.user = user; + teacher.biography = BIO + teacher.introductory_video = "youtube.com?xyz" + teacher.save.should be_true + t = Teacher.find(teacher.id) + t.biography.should == BIO + t.introductory_video.should == "youtube.com?xyz" + end + end + + describe "using save_teacher can create" do + it "a simple teacher" do + teacher = Teacher.save_teacher( + user, + biography: BIO, + introductory_video: "youtube.com?xyz" + ) + + teacher.should_not be_nil + teacher.id.should_not be_nil + t = Teacher.find(teacher.id) + t.biography.should == BIO + t.introductory_video.should == "youtube.com?xyz" + end + end +end From 98110f68bcc91365033defad82962ba99fa109d5 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 27 Jul 2015 17:00:39 -0500 Subject: [PATCH 14/67] VRFS-3359 : Save and test other segments of teacher profile. --- db/up/profile_teacher.sql | 20 ++-- ruby/lib/jam_ruby/models/teacher.rb | 45 ++++++++ ruby/lib/jam_ruby/models/user.rb | 3 +- ruby/spec/factories.rb | 10 ++ ruby/spec/jam_ruby/models/teacher_spec.rb | 123 +++++++++++++++++++++- 5 files changed, 188 insertions(+), 13 deletions(-) diff --git a/db/up/profile_teacher.sql b/db/up/profile_teacher.sql index b8e0134a3..da6e12a5e 100644 --- a/db/up/profile_teacher.sql +++ b/db/up/profile_teacher.sql @@ -6,13 +6,13 @@ CREATE TABLE teachers ( years_playing SMALLINT NOT NULL DEFAULT 0, teaches_age_lower SMALLINT NOT NULL DEFAULT 0, teaches_age_upper SMALLINT NOT NULL DEFAULT 0, - website VARCHAR(1024) NULL, - biography VARCHAR(4096) NULL, teaches_beginner BOOLEAN NOT NULL DEFAULT FALSE, teaches_intermediate BOOLEAN NOT NULL DEFAULT FALSE, teaches_advanced BOOLEAN NOT NULL DEFAULT FALSE, - price_per_lesson BOOLEAN NOT NULL DEFAULT FALSE, - price_per_month BOOLEAN NOT NULL DEFAULT FALSE, + website VARCHAR(1024) NULL, + biography VARCHAR(4096) NULL, + prices_per_lesson BOOLEAN NOT NULL DEFAULT FALSE, + prices_per_month BOOLEAN NOT NULL DEFAULT FALSE, lesson_duration_30 BOOLEAN NOT NULL DEFAULT FALSE, lesson_duration_45 BOOLEAN NOT NULL DEFAULT FALSE, lesson_duration_60 BOOLEAN NOT NULL DEFAULT FALSE, @@ -20,11 +20,11 @@ CREATE TABLE teachers ( lesson_duration_120 BOOLEAN NOT NULL DEFAULT FALSE, price_per_lesson_cents INT NULL, price_per_month_cents INT NULL, - lesson_duration_30_cents INT NULL, - lesson_duration_45_cents INT NULL, - lesson_duration_60_cents INT NULL, - lesson_duration_90_cents INT NULL, - lesson_duration_120_cents INT NULL, + price_duration_30_cents INT NULL, + price_duration_45_cents INT NULL, + price_duration_60_cents INT NULL, + price_duration_90_cents INT NULL, + price_duration_120_cents INT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); @@ -66,7 +66,7 @@ CREATE TABLE teachers_subjects( teacher_id VARCHAR(64) REFERENCES teachers(id) ON DELETE CASCADE, subject_id VARCHAR(64) REFERENCES subjects(id) ON DELETE CASCADE ); -CREATE TABLE teacher_languages( +CREATE TABLE teachers_languages( teacher_id VARCHAR(64) REFERENCES teachers(id) ON DELETE CASCADE, language_id VARCHAR(64) REFERENCES languages(id) ON DELETE CASCADE ); diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb index 431635cbd..a4e42a73b 100644 --- a/ruby/lib/jam_ruby/models/teacher.rb +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -31,6 +31,51 @@ module JamRuby teacher.website = params[:website] if params.key?(:website) teacher.biography = params[:biography] if params.key?(:biography) teacher.introductory_video = params[:introductory_video] if params.key?(:introductory_video) + + teacher.introductory_video = params[:introductory_video] if params.key?(:introductory_video) + teacher.years_teaching = params[:years_teaching] if params.key?(:years_teaching) + teacher.years_playing = params[:years_playing] if params.key?(:years_playing) + teacher.teaches_age_lower = params[:teaches_age_lower] if params.key?(:teaches_age_lower) + teacher.teaches_age_upper = params[:teaches_age_upper] if params.key?(:teaches_age_upper) + teacher.website = params[:website] if params.key?(:website) + teacher.biography = params[:biography] if params.key?(:biography) + teacher.teaches_beginner = params[:teaches_beginner] if params.key?(:teaches_beginner) + teacher.teaches_intermediate = params[:teaches_intermediate] if params.key?(:teaches_intermediate) + teacher.teaches_advanced = params[:teaches_advanced] if params.key?(:teaches_advanced) + teacher.prices_per_lesson = params[:prices_per_lesson] if params.key?(:prices_per_lesson) + teacher.prices_per_month = params[:prices_per_month] if params.key?(:prices_per_month) + teacher.lesson_duration_30 = params[:lesson_duration_30] if params.key?(:lesson_duration_30) + teacher.lesson_duration_45 = params[:lesson_duration_45] if params.key?(:lesson_duration_45) + teacher.lesson_duration_60 = params[:lesson_duration_60] if params.key?(:lesson_duration_60) + teacher.lesson_duration_90 = params[:lesson_duration_90] if params.key?(:lesson_duration_90) + teacher.lesson_duration_120 = params[:lesson_duration_120] if params.key?(:lesson_duration_120) + teacher.price_per_lesson_cents = params[:price_per_lesson_cents] if params.key?(:price_per_lesson_cents) + teacher.price_per_month_cents = params[:price_per_month_cents] if params.key?(:price_per_month_cents) + teacher.price_duration_30_cents = params[:price_duration_30_cents] if params.key?(:price_duration_30_cents) + teacher.price_duration_45_cents = params[:price_duration_45_cents] if params.key?(:price_duration_45_cents) + teacher.price_duration_60_cents = params[:price_duration_60_cents] if params.key?(:price_duration_60_cents) + teacher.price_duration_90_cents = params[:price_duration_90_cents] if params.key?(:price_duration_90_cents) + teacher.price_duration_120_cents = params[:price_duration_120_cents] if params.key?(:price_duration_120_cents) + + # Many-to-many relations: + teacher.genres = params[:genres].collect{|genre_id|Genre.find(genre_id)} if params[:genres].present? + teacher.instruments = params[:instruments].collect{|instrument_id|Instrument.find(instrument_id)} if params[:instruments].present? + teacher.subjects = params[:subjects].collect{|subject_id|Subject.find(subject_id)} if params[:subjects].present? + teacher.languages = params[:languages].collect{|language_id|Language.find(language_id)} if params[:languages].present? + + # Experience: + if params[:experience].present? + teacher.teacher_experiences = params[:experience].collect do |exp| + TeacherExperience.new( + name: exp[:name], + experience_type: exp[:experience_type], + organization: exp[:organization], + start_year: exp[:start_year], + end_year: exp[:end_year] + ) + end + end + teacher end end diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 217c73339..a7ae1b124 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -62,7 +62,8 @@ module JamRuby # bands has_many :band_musicians, :class_name => "JamRuby::BandMusician" has_many :bands, :through => :band_musicians, :class_name => "JamRuby::Band" - + has_one :teacher, :class_name => "JamRuby::Teacher" + # genres has_many :genre_players, as: :player, class_name: "JamRuby::GenrePlayer", dependent: :destroy has_many :genres, through: :genre_players, class_name: "JamRuby::Genre" diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 90cd48c44..626b906fb 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -223,6 +223,16 @@ FactoryGirl.define do description { |n| "Genre #{n}" } end + factory :language, :class => JamRuby::Language do + name { |n| "Language #{n}" } + description { |n| "Language #{n}" } + end + + factory :subject, :class => JamRuby::Subject do + name { |n| "Subject #{n}" } + description { |n| "Subject #{n}" } + end + factory :join_request, :class => JamRuby::JoinRequest do text 'let me in to the session!' end diff --git a/ruby/spec/jam_ruby/models/teacher_spec.rb b/ruby/spec/jam_ruby/models/teacher_spec.rb index f6eca828a..822c6826d 100644 --- a/ruby/spec/jam_ruby/models/teacher_spec.rb +++ b/ruby/spec/jam_ruby/models/teacher_spec.rb @@ -5,6 +5,15 @@ describe Teacher do let(:user) { FactoryGirl.create(:user) } let(:user2) { FactoryGirl.create(:user) } let(:fan) { FactoryGirl.create(:fan) } + let(:genre1) { FactoryGirl.create(:genre) } + let(:genre2) { FactoryGirl.create(:genre) } + let(:subject1) { FactoryGirl.create(:subject) } + let(:subject2) { FactoryGirl.create(:subject) } + let(:language1) { FactoryGirl.create(:language) } + let(:language2) { FactoryGirl.create(:language) } + let(:instrument1) { FactoryGirl.create(:instrument, :description => 'a great instrument')} + let(:instrument2) { FactoryGirl.create(:instrument, :description => 'an ok instrument')} + BIO = "Once a man learned a guitar." describe "can create" do it "a simple teacher" do @@ -17,14 +26,28 @@ describe Teacher do t.biography.should == BIO t.introductory_video.should == "youtube.com?xyz" end + + + it "a simple teacher" do + teacher = user.build_teacher + teacher.instruments << instrument1 + teacher.instruments << instrument2 + teacher.save.should be_true + puts teacher.errors.messages.inspect + t = Teacher.find(teacher.id) + t.instruments.should have(2).items + end + end describe "using save_teacher can create" do - it "a simple teacher" do + it "introduction" do teacher = Teacher.save_teacher( user, biography: BIO, - introductory_video: "youtube.com?xyz" + introductory_video: "youtube.com?xyz", + years_teaching: 21, + years_playing: 12 ) teacher.should_not be_nil @@ -32,6 +55,102 @@ describe Teacher do t = Teacher.find(teacher.id) t.biography.should == BIO t.introductory_video.should == "youtube.com?xyz" + t.years_teaching.should == 21 + t.years_playing.should == 12 end + + it "basics" do + teacher = Teacher.save_teacher( + user, + instruments: [instrument1, instrument2], + subjects: [subject1, subject2], + genres: [genre1, genre2], + languages: [language1, language2], + teaches_age_lower: 10, + teaches_age_upper: 20, + teaches_beginner: true, + teaches_intermediate: false, + teaches_advanced: true + ) + + t = Teacher.find(teacher.id) + + # Instruments + t.instruments.should have(2).items + + # Genres + t.genres.should have(2).items + + # Subjects + t.subjects.should have(2).items + + # Languages + t.languages.should have(2).items + + t.teaches_age_lower.should == 10 + t.teaches_age_upper.should == 20 + t.teaches_beginner.should be_true + t.teaches_intermediate.should be_false + t.teaches_advanced.should be_true + + end + + it "experience" do + experience = [{ + experience_type: "teaching", + name: "Professor", + organization: "SHSU", + start_year: 1994, + end_year: 2004 + } + ] + + teacher = Teacher.save_teacher(user, experience: experience) + teacher.should_not be_nil + t = Teacher.find(teacher.id) + t.should_not be_nil + + t.teacher_experiences.should have(1).items + end + + it "lesson pricing" do + teacher = Teacher.save_teacher( + user, + prices_per_lesson: true, + prices_per_month: true, + lesson_duration_30: true, + lesson_duration_45: true, + lesson_duration_60: true, + lesson_duration_90: true, + lesson_duration_120: true, + price_per_lesson_cents: 3000, + price_per_month_cents: 3000, + price_duration_30_cents: 3000, + price_duration_45_cents: 3000, + price_duration_60_cents: 3000, + price_duration_90_cents: 3000, + price_duration_120_cents: 3000 + ) + + teacher.should_not be_nil + teacher.id.should_not be_nil + t = Teacher.find(teacher.id) + t.prices_per_lesson.should be_true + t.prices_per_month.should be_true + t.lesson_duration_30.should be_true + t.lesson_duration_45.should be_true + t.lesson_duration_60.should be_true + t.lesson_duration_90.should be_true + t.lesson_duration_120.should be_true + t.price_per_lesson_cents.should == 3000 + t.price_per_month_cents.should == 3000 + t.price_duration_30_cents.should == 3000 + t.price_duration_45_cents.should == 3000 + t.price_duration_60_cents.should == 3000 + t.price_duration_90_cents.should == 3000 + t.price_duration_120_cents.should == 3000 + end + + end end From c89d53790439daf354d585c7d3f1fca658aa1786 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 28 Jul 2015 14:00:14 -0500 Subject: [PATCH 15/67] VRFS-3359 : Teacher profile, staged validations and specs. --- ruby/lib/jam_ruby/models/teacher.rb | 30 ++++++++ ruby/spec/jam_ruby/models/teacher_spec.rb | 91 ++++++++++++++++++++++- 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb index a4e42a73b..0f6447411 100644 --- a/ruby/lib/jam_ruby/models/teacher.rb +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -2,6 +2,7 @@ module JamRuby class Teacher < ActiveRecord::Base include HtmlSanitize html_sanitize strict: [:biography, :website] + attr_accessor :validate_introduction, :validate_basics, :validate_pricing has_and_belongs_to_many :genres, :class_name => "JamRuby::Genre", :join_table => "teachers_genres" has_and_belongs_to_many :instruments, :class_name => "JamRuby::Instrument", :join_table => "teachers_instruments" has_and_belongs_to_many :subjects, :class_name => "JamRuby::Subject", :join_table => "teachers_subjects" @@ -10,6 +11,17 @@ module JamRuby belongs_to :user, :class_name => 'JamRuby::User' validates :user, :presence => true + validates :biography, length: {minimum: 5, maximum: 4096}, :if => :validate_introduction + validates :years_teaching, :presence => true, :if => :validate_introduction + validates :years_playing, :presence => true, :if => :validate_introduction + + validates :instruments, :length => { minimum:1, message:"At least one instrument or subject is required"}, if: :validate_basics, unless: ->(teacher){teacher.subjects.length>0} + validates :subjects, :length => { minimum:1, message:"At least one instrument or subject is required"}, if: :validate_basics, unless: ->(teacher){teacher.instruments.length>0} + validates :genres, :length => { minimum:1, message:"At least one genre is required"}, if: :validate_basics + validates :languages, :length => { minimum:1, message:"At least one language is required"}, if: :validate_basics + + validate :offer_pricing, :if => :validate_pricing + validate :offer_duration, :if => :validate_pricing class << self def save_teacher(user, params) @@ -76,8 +88,26 @@ module JamRuby end end + # How to validate: + teacher.validate_introduction = !!params[:validate_introduction] + teacher.validate_basics = !!params[:validate_basics] + teacher.validate_pricing = !!params[:validate_pricing] + teacher end end + + def offer_pricing + unless prices_per_lesson.present? || prices_per_month.present? + errors.add(:offer_pricing, "Must choose to price per lesson or per month") + end + end + + def offer_duration + unless lesson_duration_30.present? || lesson_duration_45.present? || lesson_duration_60.present? || lesson_duration_90.present? || lesson_duration_120.present? + errors.add(:offer_duration, "Must offer at least one duration") + end + end + end end diff --git a/ruby/spec/jam_ruby/models/teacher_spec.rb b/ruby/spec/jam_ruby/models/teacher_spec.rb index 822c6826d..8bd27800e 100644 --- a/ruby/spec/jam_ruby/models/teacher_spec.rb +++ b/ruby/spec/jam_ruby/models/teacher_spec.rb @@ -49,8 +49,8 @@ describe Teacher do years_teaching: 21, years_playing: 12 ) - teacher.should_not be_nil + teacher.errors.should be_empty teacher.id.should_not be_nil t = Teacher.find(teacher.id) t.biography.should == BIO @@ -73,6 +73,9 @@ describe Teacher do teaches_advanced: true ) + teacher.should_not be_nil + teacher.errors.should be_empty + t = Teacher.find(teacher.id) # Instruments @@ -107,6 +110,8 @@ describe Teacher do teacher = Teacher.save_teacher(user, experience: experience) teacher.should_not be_nil + teacher.errors.should be_empty + t = Teacher.find(teacher.id) t.should_not be_nil @@ -134,6 +139,8 @@ describe Teacher do teacher.should_not be_nil teacher.id.should_not be_nil + teacher.errors.should be_empty + t = Teacher.find(teacher.id) t.prices_per_lesson.should be_true t.prices_per_month.should be_true @@ -150,7 +157,83 @@ describe Teacher do t.price_duration_90_cents.should == 3000 t.price_duration_120_cents.should == 3000 end - - end -end + + describe "validates" do + it "introduction" do + teacher = Teacher.save_teacher( + user, + years_teaching: 21, + validate_introduction: true + ) + + teacher.should_not be_nil + teacher.id.should be_nil + teacher.errors.should_not be_empty + + teacher.errors.should have_key(:biography) + end + + it "basics" do + teacher = Teacher.save_teacher( + user, + # instruments: [instrument1, instrument2], + # subjects: [subject1, subject2], + # genres: [genre1, genre2], + # languages: [language1, language2], + validate_basics: true, + teaches_age_lower: 10, + teaches_beginner: true, + teaches_intermediate: false, + teaches_advanced: true + ) + + puts "basic: #{teacher.errors.inspect}" + teacher.should_not be_nil + teacher.id.should be_nil + teacher.errors.should have_key(:instruments) + teacher.errors.should have_key(:subjects) + teacher.errors.should have_key(:genres) + teacher.errors.should have_key(:languages) + end + + it "pricing" do + teacher = Teacher.save_teacher( + user, + prices_per_lesson: false, + prices_per_month: false, + lesson_duration_30: false, + lesson_duration_45: false, + lesson_duration_60: false, + lesson_duration_90: false, + lesson_duration_120: false, + price_per_lesson_cents: 3000, + price_per_month_cents: 3000, + #price_duration_30_cents: 3000, + price_duration_45_cents: 3000, + #price_duration_60_cents: 3000, + #price_duration_90_cents: 3000, + price_duration_120_cents: 3000, + validate_pricing:true + ) + + teacher.should_not be_nil + teacher.id.should be_nil + teacher.errors.should_not be_empty + teacher.errors.should have_key(:offer_pricing) + teacher.errors.should have_key(:offer_duration) + + teacher = Teacher.save_teacher( + user, + prices_per_month: true, + lesson_duration_45: true, + validate_pricing:true + ) + + teacher.should_not be_nil + teacher.id.should_not be_nil + teacher.errors.should be_empty + + end # pricing + end # validates +end # spec From 968722bf8042de36a9185876ae316a930764ec56 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sat, 8 Aug 2015 15:00:16 -0500 Subject: [PATCH 16/67] VRFS-3359 : Routes and controller API, with test. --- .../controllers/api_teachers_controller.rb | 45 ++++ web/config/routes.rb | 7 + .../api_teachers_controller_spec.rb | 198 ++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 web/app/controllers/api_teachers_controller.rb create mode 100644 web/spec/controllers/api_teachers_controller_spec.rb diff --git a/web/app/controllers/api_teachers_controller.rb b/web/app/controllers/api_teachers_controller.rb new file mode 100644 index 000000000..04bc3f4be --- /dev/null +++ b/web/app/controllers/api_teachers_controller.rb @@ -0,0 +1,45 @@ +class ApiTeachersController < ApiController + + before_filter :api_signed_in_user, :except => [:index, :show] + before_filter :auth_teacher, :only => [:update, :delete] + + respond_to :json + + def index + @teachers = Teacher.paginate(page: params[:page]) + end + + def show + @teacher = Teacher.find(params[:id]) + respond_with_model(@teacher) + end + + def delete + @teacher.try(:destroy) + respond_with @teacher, responder => ApiResponder + end + + def create + @teacher = Teacher.save_teacher(current_user, params) + respond_with_model(@teacher, new: true, location: lambda { return api_teacher_detail_url(@teacher.id) }) + end + + def update + @teacher = Teacher.save_teacher(current_user, params) + respond_with_model(@teacher) + end + +private + def auth_teacher + if current_user.admin + @teacher = Teacher.find(params[:id]) + else + @teacher = Teacher.where("user_id=? AND id=?", current_user.id, params[:id]).first + end + + unless @teacher + Rails.logger.info("Could not find teacher #{params[:id]} for #{current_user}") + raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + end + end +end diff --git a/web/config/routes.rb b/web/config/routes.rb index 2281f6937..e0c2544c7 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -430,6 +430,13 @@ SampleApp::Application.routes.draw do match '/bands/:id' => 'api_bands#update', :via => :post match '/bands/:id' => 'api_bands#delete', :via => :delete + # teachers + match '/teachers' => 'api_teachers#index', :via => :get + match '/teachers/:id' => 'api_teachers#show', :via => :get, :as => 'api_teacher_detail' + match '/teachers' => 'api_teachers#create', :via => :post + match '/teachers/:id' => 'api_teachers#update', :via => :post + match '/teachers/:id' => 'api_teachers#delete', :via => :delete + # photo match '/bands/:id/photo' => 'api_bands#update_photo', :via => :post match '/bands/:id/photo' => 'api_bands#delete_photo', :via => :delete diff --git a/web/spec/controllers/api_teachers_controller_spec.rb b/web/spec/controllers/api_teachers_controller_spec.rb new file mode 100644 index 000000000..62d5c88c6 --- /dev/null +++ b/web/spec/controllers/api_teachers_controller_spec.rb @@ -0,0 +1,198 @@ +require 'spec_helper' + +describe ApiTeachersController do + render_views + BIO = "Once a man learned a guitar." + + let(:user) { FactoryGirl.create(:user) } + let(:genre1) { FactoryGirl.create(:genre) } + let(:genre2) { FactoryGirl.create(:genre) } + let(:subject1) { FactoryGirl.create(:subject) } + let(:subject2) { FactoryGirl.create(:subject) } + let(:language1) { FactoryGirl.create(:language) } + let(:language2) { FactoryGirl.create(:language) } + let(:instrument1) { FactoryGirl.create(:instrument, :description => 'a great instrument')} + let(:instrument2) { FactoryGirl.create(:instrument, :description => 'an ok instrument')} + + before(:each) do + controller.current_user = user + end + + after(:all) do + User.destroy_all + Teacher.destroy_all + end + + describe "creates" do + it "simple" do + post :create, biography: BIO, format: 'json' + response.should be_success + t = Teacher.find_by_user_id(user) + t.should_not be_nil + + t.biography.should == BIO + end + + + it "with instruments" do + post :create, biography: BIO, instruments: [instrument1, instrument2], format: 'json' + response.should be_success + t = Teacher.find_by_user_id(user) + t.biography.should == BIO + t.instruments.should have(2).items + end + + it "with child records" do + params = { + subjects: [subject1, subject2], + genres: [genre1, genre2], + languages: [language1, language2], + teaches_age_lower: 10, + teaches_age_upper: 20, + teaches_beginner: true, + teaches_intermediate: false, + teaches_advanced: true, + format: 'json' + } + + post :create, params + + response.should be_success + t = Teacher.find_by_user_id(user) + + # Genres + t.genres.should have(2).items + + # Subjects + t.subjects.should have(2).items + + # Languages + t.languages.should have(2).items + + t.teaches_age_lower.should == 10 + t.teaches_age_upper.should == 20 + t.teaches_beginner.should be_true + t.teaches_intermediate.should be_false + t.teaches_advanced.should be_true + end + end + + describe "to existing" do + before :each do + @teacher = Teacher.save_teacher( + user, + years_teaching: 21, + biography: BIO + ) + @teacher.should_not be_nil + @teacher.id.should_not be_nil + end + + it "deletes" do + delete :delete, {:format => 'json', id: @teacher.id} + response.should be_success + t = Teacher.find_by_user_id(user) + t.should be_nil + end + + it "with child records" do + params = { + id: @teacher.id, + subjects: [subject1, subject2], + genres: [genre1, genre2], + languages: [language1, language2], + teaches_age_lower: 10, + teaches_age_upper: 20, + teaches_beginner: true, + teaches_intermediate: false, + teaches_advanced: true, + format: 'json' + } + + post :update, params + + response.should be_success + t = Teacher.find_by_user_id(user) + + # Genres + t.genres.should have(2).items + + # Subjects + t.subjects.should have(2).items + + # Languages + t.languages.should have(2).items + + t.teaches_age_lower.should == 10 + t.teaches_age_upper.should == 20 + t.teaches_beginner.should be_true + t.teaches_intermediate.should be_false + t.teaches_advanced.should be_true + end + end + + describe "validates" do + it "introduction" do + post :create, validate_introduction: true, format: 'json' + response.should_not be_success + response.status.should eq(422) + json = JSON.parse(response.body) + json['errors'].should have_key('biography') + + end + + it "basics" do + post :create, validate_basics: true, format: 'json' + response.should_not be_success + response.status.should eq(422) + json = JSON.parse(response.body) + json['errors'].should have_key('instruments') + json['errors'].should have_key('subjects') + json['errors'].should have_key('genres') + json['errors'].should have_key('languages') + + post :create, instruments: [instrument1, instrument2], validate_basics: true, format: 'json' + response.should_not be_success + response.status.should eq(422) + json = JSON.parse(response.body) + json['errors'].should_not have_key('subjects') + json['errors'].should_not have_key('instruments') + json['errors'].should have_key('genres') + json['errors'].should have_key('languages') + end + + it "pricing" do + params = { + prices_per_lesson: false, + prices_per_month: false, + lesson_duration_30: false, + lesson_duration_45: false, + lesson_duration_60: false, + lesson_duration_90: false, + lesson_duration_120: false, + price_per_lesson_cents: 3000, + price_per_month_cents: 3000, + price_duration_45_cents: 3000, + price_duration_120_cents: 3000, + validate_pricing: true, + format: 'json' + } + post :create, params + response.should_not be_success + response.status.should eq(422) + json = JSON.parse(response.body) + json['errors'].should have_key('offer_pricing') + json['errors'].should have_key('offer_duration') + + # Add lesson duration and resubmit. We should only get one error now: + params[:lesson_duration_45] = true + post :create, params + response.should_not be_success + response.status.should eq(422) + json = JSON.parse(response.body) + json['errors'].should have_key('offer_pricing') + json['errors'].should_not have_key('offer_duration') + #puts "JSON.pretty_generate(json): #{JSON.pretty_generate(json)}" + end + end +end From d76899be5440c26b97180a8b55536d25fcc81993 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sat, 8 Aug 2015 15:00:44 -0500 Subject: [PATCH 17/67] VRFS-3359 : Teacher profile REST functions. --- web/app/assets/javascripts/jam_rest.js | 57 +++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 0f1445290..22ca5db52 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -476,6 +476,58 @@ }); } + + function getTeacher(options) { + // var url = '/api/teacher/detail' + + // if(options && _.size(options) > 0) { + // console.log("WTF"); + // url += "?" + $.param(options) + // } + + // console.log("THE URL", url) + return $.ajax({ + type: "GET", + dataType: "json", + url: '/api/teachers/detail?'+ $.param(options), + contentType: 'application/json', + processData: false + }); + } + + function deleteTeacher(teacherId) { + var url = "/api/teachers/" + teacherId; + return $.ajax({ + type: "DELETE", + dataType: "json", + url: url, + contentType: 'application/json', + processData:false + }); + } + + function updateTeacher(teacher) { + console.log("Updating teacher", teacher) + var id = teacher && teacher["id"] + var url + if (id != null && typeof(id) != 'undefined') { + url = '/api/teachers/' + teacher.id + } else { + url = '/api/teachers' + } + + var deferred = $.ajax({ + type: "POST", + dataType: "json", + url: url, + contentType: 'application/json', + processData: false, + data: JSON.stringify(teacher) + }) + + return deferred + } + function getSession(id) { var url = "/api/sessions/" + id; return $.ajax({ @@ -989,7 +1041,7 @@ type: 'GET', dataType: "json", url: "/api/feeds?" + $.param(options), - processData:false + processData:false }) } @@ -1945,6 +1997,9 @@ this.getBandPhotoFilepickerPolicy = getBandPhotoFilepickerPolicy; this.getBand = getBand; this.validateBand = validateBand; + this.getTeacher = getTeacher; + this.updateTeacher = updateTeacher; + this.deleteTeacher = deleteTeacher; this.updateFavorite = updateFavorite; this.createBandInvitation = createBandInvitation; this.updateBandInvitation = updateBandInvitation; From 176f2b9e2fa83bed8e2e7b69a7352eb9bb3b76d3 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sat, 8 Aug 2015 15:01:22 -0500 Subject: [PATCH 18/67] VRFS-3359 : Initial react components. --- .../SessionMediaTracks.js.jsx.coffee | 7 +- .../SessionMetronome.js.jsx.coffee | 1 + .../TeacherProfile.js.jsx.coffee | 99 +++++++++++++++++++ .../TeacherSetupBasics.js.jsx.coffee | 85 ++++++++++++++++ .../TeacherSetupNav.js.jsx.coffee | 25 +++++ .../actions/TeacherActions.js.coffee | 8 ++ .../mixins/TeacherMixin.js.coffee | 14 +++ .../stores/TeacherStore.js.coffee | 59 +++++++++++ 8 files changed, 294 insertions(+), 4 deletions(-) create mode 100644 web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee create mode 100644 web/app/assets/javascripts/react-components/TeacherSetupBasics.js.jsx.coffee create mode 100644 web/app/assets/javascripts/react-components/TeacherSetupNav.js.jsx.coffee create mode 100644 web/app/assets/javascripts/react-components/actions/TeacherActions.js.coffee create mode 100644 web/app/assets/javascripts/react-components/mixins/TeacherMixin.js.coffee create mode 100644 web/app/assets/javascripts/react-components/stores/TeacherStore.js.coffee diff --git a/web/app/assets/javascripts/react-components/SessionMediaTracks.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionMediaTracks.js.jsx.coffee index f72068f29..d4d50a8ae 100644 --- a/web/app/assets/javascripts/react-components/SessionMediaTracks.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionMediaTracks.js.jsx.coffee @@ -263,10 +263,9 @@ ChannelGroupIds = context.JK.ChannelGroupIds # All the JamTracks mediaTracks.push(``) - # this is not ready yet until VRFS-3363 is done - #if @state.metronome? - # @state.metronome.mode = MIX_MODES.PERSONAL - # mediaTracks.push(``) + if @state.metronome? + @state.metronome.mode = MIX_MODES.PERSONAL + mediaTracks.push(``) for jamTrack in @state.jamTracks jamTrack.mode = MIX_MODES.PERSONAL diff --git a/web/app/assets/javascripts/react-components/SessionMetronome.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionMetronome.js.jsx.coffee index d1231642d..aa7654c7e 100644 --- a/web/app/assets/javascripts/react-components/SessionMetronome.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionMetronome.js.jsx.coffee @@ -33,6 +33,7 @@ MIX_MODES = context.JK.MIX_MODES "session-track" : true "metronome" : true "no-mixer" : @props.mode == MIX_MODES.MASTER # show it as disabled if in master mode + "in-jam-track" : @props.location == 'jam-track' }) pan = if mixers.mixer? then mixers.mixer?.pan else 0 diff --git a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee new file mode 100644 index 000000000..3576b27a5 --- /dev/null +++ b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee @@ -0,0 +1,99 @@ +context = window +teacherActions = window.JK.Actions.Teacher +logger = context.JK.logger +rest = window.JK.Rest() + +@TeacherProfile = React.createClass({ + mixins: [ + @TeacherMixin, + Reflux.listenTo(@AppStore,"onAppInit"), + Reflux.listenTo(TeacherStore, "onTeacherStateChanged") + ] + + setTeacherError: () -> + + + getInitialState: () -> + {} + + beforeShow: (data) -> + logger.debug("TeacherProfile beforeShow", data, data.d) + + + if data? && data.d? + @teacherId = data.d + teacherActions.load.trigger({teacher_id: @teacherId}) + else + teacherActions.load.trigger({}) + + onTeacherStateChanged: (changes) -> + $root = jQuery(this.getDOMNode()) + logger.debug("onTeacherStateChanged", changes, changes.errors?, changes.errors) + $(".error-text", $root).remove() + $(".input-error", $root).removeClass("input-error") + if changes.errors? + for k,v of changes.errors + logger.debug("error", k, v) + teacherField = $root.find(".teacher-field[name='#{k}']") + teacherField.append("
#{v.join()}
") + $("input", teacherField).addClass("input-error") + #$(".error-text", teacherField).show() + + else + teacher = changes.teacher + logger.debug("@teacher", teacher) + this.setState({ + biography: teacher.biography, + introductory_video: teacher.introductory_video, + years_teaching: teacher.years_teaching, + years_playing: teacher.years_playing, + validate_introduction: true + }) + + + + captureFormState: (changes) -> + $root = jQuery(this.getDOMNode()) + this.setState({ + biography: $root.find(".teacher-biography").val(), + introductory_video: $root.find(".teacher-introductory-video").val(), + years_teaching: $root.find(".years-teaching-experience").val(), + years_playing: $root.find(".years-playing-experience").val() + }); + logger.debug("capturedFormState", this.state, changes) + + handleSave: (e) -> + logger.debug("HANDLESAVE: ", this.state, this, e) + teacherActions.change.trigger(this.state) + + render: () -> + logger.debug("RENDERING", this.props, this.state) + `
+
+
+ +