require 'spec_helper'


describe Praxis::Mapper::SelectorGenerator do
  let(:resource) { SimpleResource }
  subject(:generator) {described_class.new }

  context '#add' do
    let(:resource) { SimpleResource }
    shared_examples 'a proper selector' do
      it { expect(generator.add(resource, fields).selectors.dump).to be_deep_equal selectors }
    end
    
    context 'basic combos' do
      context 'direct column fields' do
        let(:fields) { {id: true, foobar: true} }
        let(:selectors) do 
          {
            model: SimpleModel,
            columns: [:id, :foobar]
          } 
        end
        it_behaves_like 'a proper selector'
      end

      context 'aliased column fields' do
        let(:fields) { {id: true, name: true} }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:id, :simple_name]
          }
        end
        it_behaves_like 'a proper selector'
      end

      context 'pure associations without recursion' do
        let(:fields) { {other_model: true} }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:other_model_id], # FK of the other_model association
            tracks: {
              other_model: { 
                columns: [:id], # joining key for the association
                model: OtherModel 
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end

      context 'aliased associations without recursion' do
        let(:fields) { {other_resource: true} }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:other_model_id], # FK of the other_model association
            tracks: {
              other_model: { 
                columns: [:id], # joining key for the association
                model: OtherModel 
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end
      context 'aliased associations without recursion (that map to columns and other associations)' do
        let(:fields) { {aliased_method: true} }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:column1, :other_model_id], # other_model_id => because of the association
            tracks: {
              other_model: { 
                columns: [:id], # joining key for the association
                model: OtherModel 
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end

      context 'redefined associations that add some extra columns (would need both the underlying association AND the columns in place)' do
        let(:fields) { {parent: true} }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:parent_id, :added_column],
            tracks: {
              parent: { 
                columns: [:id],
                model: ParentModel
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end

      context 'a simple property that requires all fields' do
        let(:fields) { {everything: true} }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:*],
          }
        end
        it_behaves_like 'a proper selector'
      end

      context 'a simple property that requires itself' do
        let(:fields) { {circular_dep: true} }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:circular_dep, :column1], #allows to "expand" the dependency into itself + others
          }
        end
        it_behaves_like 'a proper selector'
      end

      context 'a simple property without dependencies' do
        let(:fields) { {no_deps: true} }
        let(:selectors) do
          {
            model: SimpleModel
          }
        end
        it_behaves_like 'a proper selector'
      end

    end

    context 'nested tracking' do
      context 'pure associations follow the nested fields' do
        let(:fields) do
          { 
            other_model: {
              id: true
            }
          }
        end
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:other_model_id],
            tracks: {
              other_model: {
                model: OtherModel,
                columns: [:id]
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end

      context 'Aliased resources disregard any nested fields...' do
        let(:fields) do
          {
            other_resource: {
              id: true
            }
          }
        end
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:other_model_id],
            tracks: {
              other_model: {
                model: OtherModel,
                columns: [:id]
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end

      context 'merging multiple tracks with the same name within a node' do
        let(:fields) do
          { # Both everything_from_parent and parent will track the underlying 'parent' assoc
            # ...and the final respective fields and tracks will need to be merged together.
            # columns will be merged by just *, and tracks will merge true with simple children
            everything_from_parent: true,
            parent: {
              simple_children: true
            }
          }
        end
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:parent_id, :added_column],
            tracks: {
              parent: {
                model: ParentModel,
                columns: [:*],
                tracks: {
                  simple_children: {
                    model: SimpleModel,
                    columns: [:parent_id]
                  }
                }
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end
    end

    context 'string associations' do
      context 'that specify a direct existing colum in the target dependency' do
        let(:fields) { { direct_other_name: true } }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:other_model_id],
            tracks: {
              other_model: {
                model: OtherModel,
                columns: [:id, :name]
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end

      context 'that specify an aliased property in the target dependency' do
        let(:fields) { { aliased_other_name: true } }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:other_model_id],
            tracks: {
              other_model: {
                model: OtherModel,
                columns: [:id, :name]
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end

      context 'for a property that requires all fields from an association' do
        let(:fields) { {everything_from_parent: true} }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:parent_id],
            tracks: {
              parent: { 
                model: ParentModel,
                columns: [:*]
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end
    end

    context 'required extra select fields due to associations' do
      context 'many_to_one' do
        let(:fields) { {other_model: true} }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:other_model_id], # FK of the other_model association
            tracks: {
              other_model: { 
                columns: [:id],
                model: OtherModel 
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end
      context 'one_to_many' do
        let(:resource) { ParentResource }
        let(:fields) { {simple_children: true} }
        let(:selectors) do
          {
            model: ParentModel,
            columns: [:id], # No FKs in the source model for one_to_many
            tracks: {
              simple_children: { 
                columns: [:parent_id],
                model: SimpleModel 
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end      
      context 'many_to_many' do
        let(:resource) { OtherResource }
        let(:fields) { {simple_models: true} }
        let(:selectors) do
          {
            model: OtherModel,
            columns: [:id], #join key in the source model for many_to_many (where the middle table points to)
            tracks: {
              simple_models: { 
                columns: [:id], #join key in the target model for many_to_many (where the middle table points to)
                model: SimpleModel
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end      

      context 'that are several attriutes deep' do
        let(:fields) { { deep_nested_deps: true } }
        let(:selectors) do
          {
            model: SimpleModel,
            columns: [:parent_id],
            tracks: {
              parent: {
                model: ParentModel,
                columns: [:id], # No FKs in the source model for one_to_many
                tracks: {
                  simple_children: { 
                    columns: [:parent_id, :other_model_id],
                    model: SimpleModel,
                    tracks: {
                      other_model: {
                        model: OtherModel,
                        columns: [:id, :parent_id],
                        tracks: {
                          parent: {
                            model: ParentModel,
                            columns: [:id, :simple_name, :other_attribute]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        end
        it_behaves_like 'a proper selector'
      end
    end
  end
end
