# frozen_string_literal: true require 'spec_helper' module RailsBestPractices module Prepares describe ModelPrepare do let(:runner) { Core::Runner.new(prepares: described_class.new) } context 'models' do it 'class_name with modules ::' do content = <<-EOF class Blog::Post < ActiveRecord::Base end EOF runner.prepare('app/models/admin/post.rb', content) models = Prepares.models expect(models.map(&:to_s)).to eq(['Blog::Post']) end it 'class_name with modules' do content = <<-EOF module Blog class Post < ActiveRecord::Base end end EOF runner.prepare('app/models/admin/post.rb', content) models = Prepares.models expect(models.map(&:to_s)).to eq(['Blog::Post']) end end context 'associations' do it 'parses model associations' do content = <<-EOF class Project < ActiveRecord::Base belongs_to :portfolio has_one :project_manager has_many :milestones has_and_belongs_to_many :categories end EOF runner.prepare('app/models/project.rb', content) model_associations = Prepares.model_associations expect(model_associations.get_association('Project', 'portfolio')).to eq( 'meta' => 'belongs_to', 'class_name' => 'Portfolio' ) expect(model_associations.get_association('Project', 'project_manager')).to eq( 'meta' => 'has_one', 'class_name' => 'ProjectManager' ) expect(model_associations.get_association('Project', 'milestones')).to eq( 'meta' => 'has_many', 'class_name' => 'Milestone' ) expect(model_associations.get_association('Project', 'categories')).to eq( 'meta' => 'has_and_belongs_to_many', 'class_name' => 'Category' ) end context 'with class_name option' do it 'parses belongs_to' do content = <<-EOF class Post < ActiveRecord::Base belongs_to :author, "class_name" => "Person" end EOF runner.prepare('app/models/post.rb', content) model_associations = Prepares.model_associations expect(model_associations.get_association('Post', 'author')).to eq( 'meta' => 'belongs_to', 'class_name' => 'Person' ) end it 'parses has_one' do content = <<-EOF class Project < ActiveRecord::Base has_one :project_manager, "class_name" => "Person" end EOF runner.prepare('app/models/post.rb', content) model_associations = Prepares.model_associations expect(model_associations.get_association('Project', 'project_manager')).to eq( 'meta' => 'has_one', 'class_name' => 'Person' ) end it 'parses has_many' do content = <<-EOF class Project < ActiveRecord::Base has_many :people, "class_name" => "Person" end EOF runner.prepare('app/models/project.rb', content) model_associations = Prepares.model_associations expect(model_associations.get_association('Project', 'people')).to eq( 'meta' => 'has_many', 'class_name' => 'Person' ) end it 'parses has_and_belongs_to_many' do content = <<-EOF class Citizen < ActiveRecord::Base has_and_belongs_to_many :nations, "class_name" => "Country" end EOF runner.prepare('app/models/citizen.rb', content) model_associations = Prepares.model_associations expect(model_associations.get_association('Citizen', 'nations')).to eq( 'meta' => 'has_and_belongs_to_many', 'class_name' => 'Country' ) end context 'namespace' do it 'parses with namespace' do content = <<-EOF class Community < ActiveRecord::Base has_many :members end EOF runner.prepare('app/models/community.rb', content) content = <<-EOF class Community::Member < ActiveRecord::Base belongs_to :community end EOF runner.prepare('app/models/community/member.rb', content) runner.after_prepare model_associations = Prepares.model_associations expect(model_associations.get_association('Community', 'members')).to eq( 'meta' => 'has_many', 'class_name' => 'Community::Member' ) expect(model_associations.get_association('Community::Member', 'community')).to eq( 'meta' => 'belongs_to', 'class_name' => 'Community' ) end it 'parses without namespace' do content = <<-EOF class Community::Member::Rating < ActiveRecord::Base belongs_to :member end EOF runner.prepare('app/models/community/member/rating.rb', content) content = <<-EOF class Community::Member < ActiveRecord::Base has_many :ratings end EOF runner.prepare('app/models/community/member.rb', content) runner.after_prepare model_associations = Prepares.model_associations expect(model_associations.get_association('Community::Member::Rating', 'member')).to eq( 'meta' => 'belongs_to', 'class_name' => 'Community::Member' ) expect(model_associations.get_association('Community::Member', 'ratings')).to eq( 'meta' => 'has_many', 'class_name' => 'Community::Member::Rating' ) end end end context 'mongoid embeds' do it 'parses embeds_many' do content = <<-EOF class Person include Mongoid::Document embeds_many :addresses end EOF runner.prepare('app/models/person.rb', content) model_associations = Prepares.model_associations expect(model_associations.get_association('Person', 'addresses')).to eq( 'meta' => 'embeds_many', 'class_name' => 'Address' ) end it 'parses embeds_one' do content = <<-EOF class Lush include Mongoid::Document embeds_one :whiskey, class_name: "Drink", inverse_of: :alcoholic end EOF runner.prepare('app/models/lush.rb', content) model_associations = Prepares.model_associations expect(model_associations.get_association('Lush', 'whiskey')).to eq( 'meta' => 'embeds_one', 'class_name' => 'Drink' ) end it 'parses embedded_in' do content = <<-EOF class Drink include Mongoid::Document embedded_in :alcoholic, class_name: "Lush", inverse_of: :whiskey end EOF runner.prepare('app/models/drink.rb', content) model_associations = Prepares.model_associations expect(model_associations.get_association('Drink', 'alcoholic')).to eq( 'meta' => 'embedded_in', 'class_name' => 'Lush' ) end end context 'mongomapper many/one' do it 'parses one' do content = <<-EOF class Employee include MongoMapper::Document one :desk end EOF runner.prepare('app/models/employee.rb', content) model_associations = Prepares.model_associations expect(model_associations.get_association('Employee', 'desk')).to eq( 'meta' => 'one', 'class_name' => 'Desk' ) end it 'parses many' do content = <<-EOF class Tree include MongoMapper::Document many :birds end EOF runner.prepare('app/models/tree.rb', content) model_associations = Prepares.model_associations expect(model_associations.get_association('Tree', 'birds')).to eq('meta' => 'many', 'class_name' => 'Bird') end end end context 'methods' do it 'parses model methods' do content = <<-EOF class Post < ActiveRecord::Base def save; end def find; end end EOF runner.prepare('app/models/post.rb', content) methods = Prepares.model_methods expect(methods.get_methods('Post').map(&:method_name)).to eq(%w[save find]) end it 'parses model methods with access control' do content = <<-EOF class Post < ActiveRecord::Base def save; end def find; end protected def create_or_update; end private def find_by_sql; end end EOF runner.prepare('app/models/post.rb', content) methods = Prepares.model_methods expect(methods.get_methods('Post').map(&:method_name)).to eq(%w[save find create_or_update find_by_sql]) expect(methods.get_methods('Post', 'public').map(&:method_name)).to eq(%w[save find]) expect(methods.get_methods('Post', 'protected').map(&:method_name)).to eq(['create_or_update']) expect(methods.get_methods('Post', 'private').map(&:method_name)).to eq(['find_by_sql']) end it 'parses model methods with module ::' do content = <<-EOF class Admin::Blog::Post < ActiveRecord::Base def save; end def find; end end EOF runner.prepare('app/models/admin/blog/post.rb', content) methods = Prepares.model_methods expect(methods.get_methods('Admin::Blog::Post').map(&:method_name)).to eq(%w[save find]) end it 'parses model methods with module' do content = <<-EOF module Admin module Blog class Post < ActiveRecord::Base def save; end def find; end end end end EOF runner.prepare('app/models/admin/blog/post.rb', content) methods = Prepares.model_methods expect(methods.get_methods('Admin::Blog::Post').map(&:method_name)).to eq(%w[save find]) end it 'does not add methods from module' do content = <<-EOF class Model < ActiveRecord::Base end EOF runner.prepare('app/models/model.rb', content) content = <<-EOF module Mixin def mixed_method end end EOF runner.prepare('app/models/mixins/mixin.rb', content) methods = Prepares.model_methods expect(methods.get_methods('Model')).to be_empty end end context 'scope' do it 'treats named_scope as method' do content = <<-EOF class Post < ActiveRecord::Base named_scope :active, conditions: {active: true} end EOF runner.prepare('app/models/post.rb', content) methods = Prepares.model_methods expect(methods.get_methods('Post').map(&:method_name)).to eq(['active']) end it 'treats scope as method' do content = <<-EOF class Post < ActiveRecord::Base scope :active, where(active: true) end EOF runner.prepare('app/models/post.rb', content) methods = Prepares.model_methods expect(methods.get_methods('Post').map(&:method_name)).to eq(['active']) end end context 'alias' do it 'treats alias as method' do content = <<-EOF class Post < ActiveRecord::Base alias :new :old end EOF runner.prepare('app/models/post.rb', content) methods = Prepares.model_methods expect(methods.get_methods('Post').map(&:method_name)).to eq(['new']) end it 'treats alias_method as method' do content = <<-EOF class Post < ActiveRecord::Base alias_method :new, :old end EOF runner.prepare('app/models/post.rb', content) methods = Prepares.model_methods expect(methods.get_methods('Post').map(&:method_name)).to eq(['new']) end it 'treats alias_method_chain as method' do content = <<-EOF class Post < ActiveRecord::Base alias_method_chain :method, :feature end EOF runner.prepare('app/models/post.rb', content) methods = Prepares.model_methods expect(methods.get_methods('Post').map(&:method_name)).to eq(%w[method_with_feature method]) end end context 'attributes' do it 'parses mongoid field' do content = <<-EOF class Post include Mongoid::Document field :title field :tags, type: Array field :comments_count, type: Integer field :deleted_at, type: DateTime field :active, type: Boolean end EOF runner.prepare('app/models/post.rb', content) model_attributes = Prepares.model_attributes expect(model_attributes.get_attribute_type('Post', 'title')).to eq('String') expect(model_attributes.get_attribute_type('Post', 'tags')).to eq('Array') expect(model_attributes.get_attribute_type('Post', 'comments_count')).to eq('Integer') expect(model_attributes.get_attribute_type('Post', 'deleted_at')).to eq('DateTime') expect(model_attributes.get_attribute_type('Post', 'active')).to eq('Boolean') end it 'parses mongomapper field' do content = <<-EOF class Post include MongoMapper::Document key :first_name, String key :last_name, String key :age, Integer key :born_at, Time key :active, Boolean key :fav_colors, Array end EOF runner.prepare('app/models/post.rb', content) model_attributes = Prepares.model_attributes expect(model_attributes.get_attribute_type('Post', 'first_name')).to eq('String') expect(model_attributes.get_attribute_type('Post', 'last_name')).to eq('String') expect(model_attributes.get_attribute_type('Post', 'age')).to eq('Integer') expect(model_attributes.get_attribute_type('Post', 'born_at')).to eq('Time') expect(model_attributes.get_attribute_type('Post', 'active')).to eq('Boolean') expect(model_attributes.get_attribute_type('Post', 'fav_colors')).to eq('Array') end end context 'no error' do it 'raiseds for finder_sql option' do content = <<-EOF class EventSubscription < ActiveRecord::Base has_many :event_notification_template, finder_sql: ? end EOF content = content.sub( '?', '\'SELECT event_notification_templates.* from event_notification_templates where event_type_id=#{event_type_id} and delivery_method_id=#{delivery_method_id}\'' ) expect { runner.prepare('app/models/event_subscription.rb', content) }.not_to raise_error end end end end end