require 'spec_helper'

describe 'DataMapper::Is::Remixable' do

  supported_by :sqlite, :mysql, :postgres do

    describe 'DataMapper::Resource' do
      it "should know if it is remixable" do
        User.is_remixable?.should be(false)
        Image.is_remixable?.should be(true)
        Article.is_remixable?.should be(false)
        Commentable.is_remixable?.should be(true)
        Portfolio::Project.is_remixable?.should be(false)
      end
    end

    it "should only allow remixables to be remixed" do
      lambda { User.remix 1, :articles }.should raise_error(Exception)
    end

    it "should not allow enhancements of modules that aren't remixed" do
      lambda {
        User.enhance :images
      }.should raise_error
    end

    it "should provide a default suffix values for models that do 'is :remixable'" do
      Image.suffix.should == "image"
    end

    it "should allow enhancing a model that is remixed" do
      Article.enhance :images do
        def self.test_enhance
          true
        end
      end

      ArticleImage.should respond_to("test_enhance")
    end

    it "should allow enhancing a model that was remixed from a nested module" do
      Article.enhance :ratings do
        def self.test_enhance
          true
        end
      end

      ArticleRating.should respond_to("test_enhance")
      ArticleRating.should respond_to("total_rating")
      ArticleRating.new.should respond_to("user_id")
      ArticleRating.new.should respond_to("rating")
    end

    it "should allow enhancing the same remixable twice with different class_name attributes" do
      Article.enhance :taggable, "UserTagging" do
        def self.test_enhance
          true
        end
      end

      UserTagging.should respond_to("test_enhance")
      UserTagging.should respond_to("related_tags")
      UserTagging.new.should respond_to("user_id")
      UserTagging.new.should respond_to("tag")

      Article.enhance :taggable, "BotTagging" do
        def self.test_enhance_2
          true
        end
      end
      BotTagging.should respond_to("test_enhance_2")
      BotTagging.should respond_to("related_tags")
      BotTagging.new.should respond_to("bot_id")
      BotTagging.new.should respond_to("tag")
    end

    it "should raise an exception when enhancing an unknown class" do
      lambda {
        Article.enhance :taggable, "NonExistentClass"
      }.should raise_error
    end

    it "should provide a map of Remixable Modules to Remixed Models names" do
      User.remixables.should_not be(nil)
    end

    it "should store the remixed model in the map of Remixable Modules to Remixed Models" do
      User.remixables[:billable][:account][:model].should == Account
      # nested remixables
      User.remixables[:rating][:user_rating][:model].should == UserRating
      Article.remixables[:rating][:article_rating][:model].should == ArticleRating
      Topic.remixables[:rating][:rating][:model].should == Rating
    end

    it "should store the remixee reader name in the map of Remixable Modules to Remixed Models" do
      User.remixables[:billable][:account][:reader].should == :accounts
      # nested remixables
      User.remixables[:rating][:user_rating][:reader].should == :user_ratings
      Article.remixables[:rating][:article_rating][:reader].should == :ratings
      Topic.remixables[:rating][:rating][:reader].should == :ratings_for_topic
    end

    it "should store the remixee writer name in the map of Remixable Modules to Remixed Models" do
      User.remixables[:billable][:account][:writer].should == :accounts=
      # nested remixables
      User.remixables[:rating][:user_rating][:writer].should == :user_ratings=
      Article.remixables[:rating][:article_rating][:writer].should == :ratings=
      Topic.remixables[:rating][:rating][:writer].should == :ratings_for_topic=
    end

    it "should allow specifying an alternate class name" do
      User.remixables[:billable][:account][:model].name.should_not == "UserBillable"
      User.remixables[:billable][:account][:model].name.should == "Account"
    end

    it "should create a storage name based on the class name" do
      Article.remixables[:image][:article_image][:model].storage_names[:default].should == "article_images"
      User.remixables[:billable][:account][:model].storage_names[:default].should == "accounts"
    end

    it "should allow creating an accessor alias" do
      article = Article.new
      article.should respond_to("pics")
    end

    it "should copy properties from the Remixable Module to the Remixed Model" do
      #Billabe => Account
      account = Account.new

      account.should respond_to("cc_num")
      account.should respond_to("cc_type")
      account.should respond_to("expiration")
    end

    it "should clone hooks from the Remixable Module to the Remixed Model" do
      article = Article.new
      article.pics = (@pic = ArticleImage.new)

      expect {
        article.save
      }.to change(@pic, :hook_method_called?).from(false).to(true)
    end

    it "should copy validation contexts from the Remixable Module to the Remixed Model" do
      ArticleComment.validators.contexts.should have_key(:default)
      ArticleComment.validators.contexts.should have_key(:publish)
    end

    it "should allow 1:M relationships with the Remixable Module" do
      user = User.new
      addy = UserAddress.new
      addy2 = UserAddress.new

      user.first_name = "Jack"
      user.last_name = "Tester"

      addy.address1 = "888 West Whatnot Ave."
      addy.city = "Los Angeles"
      addy.state = "CA"
      addy.zip = "90230"

      addy2.address1 = "325 East NoWhere Lane"
      addy2.city = "Fort Myers"
      addy2.state = "FL"
      addy2.zip = "33971"

      user.user_addresses << addy
      user.user_addresses << addy2

      user.user_addresses.length.should be(2)
    end

    let(:project) { Portfolio::Project.new }

    it 'should create the correctly named relationship when using namespaced Models' do
      project.should_not respond_to(:portfolio_project_images)
      project.should_not respond_to(:portfolio_project_views)
      project.should respond_to(:project_images)
      project.should respond_to(:project_views)
    end

    it 'should allow 1:M relationships with the Remixable Module and a namespaced Model' do
      {
        '123.202.201.209' => DateTime.parse('2009/01/02'),
        '32.21.21.21'     => DateTime.parse('2010/05/12'),
        '88.999.910.101'  => DateTime.parse('1999/12/15')
      }.each do |ip, date|
        view            = Portfolio::ProjectView.new
        view.ip         = ip
        view.created_at = date
        project.project_views << view
      end

      project.project_views.length.should be(3)
    end

    it "should allow 1:1 relationships with the Remixable Module" do
      article = Article.new
      image1  = ArticleImage.new
      image2  = ArticleImage.new

      article.title = "Really important news!"
      article.url = "http://example.com/index.html"

      image1.description = "Shocking and horrific photo!"
      image1.path = "~/pictures/shocking.jpg"

      image2.description = "Other photo"
      image2.path = "~/pictures/mom_naked.yipes"

      begin
        article.pics << image1
        false
      rescue Exception => e
        e.class.should be(NoMethodError)
      end

      article.pics = image2
      article.pics.path.should == image2.path
    end

    it "should allow 1:1 relationships with the Remixable Module and a namespaced Model" do
      image1  = Portfolio::ProjectImage.new
      image2  = Portfolio::ProjectImage.new

      project.title = "My best work"
      project.url   = "http://example.com/work/success-in-five-steps.html"

      image1.description  = 'Business Failure'
      image1.path         = '~/pictures/biz.jpg'

      image2.description  = '73 Signals Sucess'
      image2.path         = '~/pictures/hhd.png'

      begin
        project.project_images << image1
        false
      rescue Exception => e
        e.class.should be(NoMethodError)
      end

      project.project_images = image2
      project.project_images.path.should == image2.path
    end

    # Example:
    #   Users are Commentable by many Users
    #
    it "should allow M:M unary relationships through the Remixable Module" do
      pending 'self referential m:m is currently not supported by dm-core'
    end

    # Example:
    #   Articles are Commentable by many Users
    #
    it "should allow M:M relationships through the Remixable Module" do
      user    = User.new
      article = Article.new
      ac      = ArticleComment.new

      user.first_name = "Talker"
      user.last_name = "OnTheInternetz"
      user.save

      article.url = "Http://example.com/"
      article.title = "Important internet thingz, lol"
      article.save

      ac.comment = "This article sux!"
      ac.user    = user
      ac.article = article
      ac.save

      article.comments.first.should == ac
      user.article_comments.first.should == ac
    end

    it 'should allow M:M relationships through the Remixable Module and a namespaced Model' do
      user    = User.new
      pc      = Portfolio::ProjectComment.new

      user.first_name = 'John'
      user.last_name  = 'Jameson'
      user.save

      project.url   = 'http://example.com/'
      project.title = 'A showcase of my best work'
      project.save

      pc.comment = 'You overprice your work!'
      pc.user    = user
      pc.project = project
      pc.save

      project.comments.first.should == pc
      user.project_comments.first.should == pc
    end

    # Example:
    # Remixable Image add functionality to any class that remixes it
    #   Image::RemixerClassMethods defines a method called 'total_images' that counts the total number of images for the class
    #   Image::RemixerInstanceMethods defines a method called 'most_viewed_image' that find the most viewed image for an object
    #
    #   User.remixes n, :images
    #   User.total_images => count of all images owned by all users
    #   User.first.most_viewed_image => would return the most viewed image
    #
    it "should add a remixables' 'RemixerClassMethods' modules to the remixing class" do
      Article.respond_to?(:test_remixer_class_method).should be(true)
      Article.test_remixer_class_method.should == 'CLASS METHOD FOR REMIXER'
    end

    it "should add a remixables' 'RemixerInstanceMethods' modules to the remixing class" do
      Article.new.respond_to?(:test_remixer_instance_method).should be(true)
      Article.new.test_remixer_instance_method.should == 'INSTANCE METHOD FOR REMIXER'
    end

    # Example:
    # Remixable Image add functionality to any class that remixes it
    #   Image::RemixeeClassMethods defines a method called 'damaged_files' would return a list of all images with invalid checksums (or whatev)
    #   Image::RemixeeInstanceMethods defines a method called 'mime_type' that find the mime type of the particular image
    #
    #   Article.remixes n, :images
    #   # => yields and ArticleImage Class
    #   ArticleImage.damaged_files => list of all images with invalid checksums
    #   ArticleImage.first.mime_type => would return the mime type of that image
    #
    it "should add a remixables' 'RemixeeClassMethods' modules to the generated remixed class" do
      ArticleImage.respond_to?(:test_remixee_class_method).should be(true)
      ArticleImage.test_remixee_class_method.should == 'CLASS METHOD FOR REMIXEE'
    end

    it "should add a remixables' 'RemixeeInstanceMethods' modules to the generated remixed class" do
      ArticleImage.new.respond_to?(:test_remixee_instance_method).should be(true)
      ArticleImage.new.test_remixee_instance_method.should == 'INSTANCE METHOD FOR REMIXEE'
    end

    # Example:
    # User.remixes n, :images, :as => "pics"
    # User.first.pics would be the acessor for images
    # User.first.user_images should raise method not found
    #
    it 'should remove the original attribute accessor when attaching an optional one' do
      Article.new.respond_to?(:pics).should be(true)
      User.new.respond_to?(:user_addresses).should be(true)
    end

    # Currently:
    #   Submission.remixes n, :comments
    #   SubmissionComment.new.user = User.first => throws exception, accessor name is 'users' instead
    #
    # Example:
    #   User.remix 1, :images
    #     # => User.image & UserImage.user
    #
    #   User.remix n, :images
    #     # => User.images & UserImage.user
    #
    #   User.remix n, :comments, :for => 'User', :via => 'commentor'
    #     # => User.comments & UserComment.user & UserComment.commentor
    #
    it 'should pluralize accessor names with respect to cardinality' do
      pending
    end

    # Note:
    # Currently the :via flag allows one to specify another name for the field, but it always appends _id
    #
    # Example:
    #   User w/ PK being 'login_name'
    #   User.remixes n, :comments, :for => 'User', :via => 'commentor'
    #
    #   Comment Table:
    #     * id
    #     * text
    #     * user_login_name
    #     * commentor_id #=> should be able to specify it to be commentor_login_name
    #
    it 'should allow the primary and child field names to be specified while remixing' do
      pending
    end

  end

end
