specstar-remarkable 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,284 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class AssociationMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
+ arguments :macro, :collection => :associations, :as => :association
6
+
7
+ optionals :through, :source, :source_type, :class_name, :foreign_key, :dependent, :join_table, :as
8
+ optionals :select, :conditions, :include, :group, :having, :order, :limit, :offset, :finder_sql, :counter_sql
9
+ optionals :uniq, :readonly, :validate, :autosave, :polymorphic, :counter_cache, :default => true
10
+ optionals :before_add, :after_add, :before_remove, :after_remove
11
+
12
+ collection_assertions :association_exists?, :macro_matches?, :through_exists?, :source_exists?,
13
+ :klass_exists?, :join_table_exists?, :foreign_key_exists?, :polymorphic_exists?,
14
+ :counter_cache_exists?, :options_match?
15
+
16
+ protected
17
+
18
+ def association_exists?
19
+ reflection
20
+ end
21
+
22
+ def macro_matches?
23
+ reflection.macro == @macro
24
+ end
25
+
26
+ def through_exists?
27
+ return true unless @options.key?(:through)
28
+ reflection.through_reflection rescue false
29
+ end
30
+
31
+ def source_exists?
32
+ return true unless @options.key?(:through)
33
+ reflection.source_reflection rescue false
34
+ end
35
+
36
+ # Polymorphic associations does not have a klass.
37
+ #
38
+ def klass_exists?
39
+ return true if @options[:polymorphic]
40
+ reflection.klass rescue nil
41
+ end
42
+
43
+ # has_and_belongs_to_many only works if the tables are in the same
44
+ # database, so we always look for the table in the subject connection.
45
+ #
46
+ def join_table_exists?
47
+ return true unless reflection.macro == :has_and_belongs_to_many
48
+ subject_class.connection.tables.include?(reflection.options[:join_table].to_s)
49
+ end
50
+
51
+ def foreign_key_exists?
52
+ return true unless foreign_key_table
53
+ table_has_column?(foreign_key_table_class, foreign_key_table, reflection_foreign_key)
54
+ end
55
+
56
+ def polymorphic_exists?
57
+ return true unless @options[:polymorphic]
58
+ klass_table_has_column?(subject_class, reflection_foreign_key.sub(/_id$/, '_type'))
59
+ end
60
+
61
+ def counter_cache_exists?
62
+ return true unless @options[:counter_cache]
63
+ klass_table_has_column?(reflection.klass, reflection.counter_cache_column.to_s)
64
+ end
65
+
66
+ def options_match?
67
+ actual_options = {}
68
+
69
+ @options.keys.each do |key|
70
+ method = :"reflection_#{key}"
71
+
72
+ @options[key] = @options[key].to_s
73
+ actual_options[key] = (respond_to?(method, true) ? send(method) : reflection.options[key]).to_s
74
+ end
75
+
76
+ return @options == actual_options, :actual => actual_options.inspect
77
+ end
78
+
79
+ private
80
+
81
+ def reflection
82
+ @reflection ||= subject_class.reflect_on_association(@association.to_sym)
83
+ end
84
+
85
+ def subject_table_name
86
+ subject_class.table_name.to_s
87
+ end
88
+
89
+ def reflection_class_name
90
+ reflection.class_name.to_s rescue nil
91
+ end
92
+
93
+ def reflection_table_name
94
+ reflection.klass.table_name.to_s rescue nil
95
+ end
96
+
97
+ def reflection_foreign_key
98
+ (reflection.respond_to?(:foreign_key) ? reflection.foreign_key : reflection.primary_key_name).to_s
99
+ end
100
+
101
+ def table_has_column?(klass, table_name, column)
102
+ klass.connection.columns(table_name, 'Remarkable column retrieval').any?{|c| c.name == column }
103
+ end
104
+
105
+ def klass_table_has_column?(klass, column)
106
+ table_has_column?(klass, klass.table_name, column)
107
+ end
108
+
109
+ # In through we don't check the foreign_key, because it's spread
110
+ # accross the through and the source reflection which should be tested
111
+ # with their own macros.
112
+ #
113
+ # In cases a join table exists (has_and_belongs_to_many and through
114
+ # associations), we check the foreign key in the join table.
115
+ #
116
+ # On belongs to, the foreign_key is in the subject class table and in
117
+ # other cases it's on the reflection class table.
118
+ #
119
+ def foreign_key_table
120
+ if reflection.options.key?(:through)
121
+ nil
122
+ elsif reflection.macro == :has_and_belongs_to_many
123
+ reflection.options[:join_table]
124
+ elsif reflection.macro == :belongs_to
125
+ subject_table_name
126
+ else
127
+ reflection_table_name
128
+ end
129
+ end
130
+
131
+ # Returns the foreign key table class to use the proper connection
132
+ # when searching for the table and foreign key.
133
+ #
134
+ def foreign_key_table_class
135
+ if [:belongs_to, :has_and_belongs_to_many].include?(reflection.macro)
136
+ subject_class
137
+ else
138
+ reflection.klass
139
+ end
140
+ end
141
+
142
+ def interpolation_options
143
+ options = {}
144
+ options[:macro] = Remarkable.t(@macro, :scope => matcher_i18n_scope, :default => @macro.to_s.gsub("_", ""))
145
+ options[:options] = @options.inspect
146
+
147
+ if @subject && reflection
148
+ options.merge!(
149
+ :actual_macro => Remarkable.t(reflection.macro, :scope => matcher_i18n_scope, :default => reflection.macro.to_s),
150
+ :subject_table => subject_table_name.inspect,
151
+ :reflection_table => reflection_table_name.inspect,
152
+ :foreign_key_table => foreign_key_table.inspect,
153
+ :polymorphic_column => reflection_foreign_key.sub(/_id$/, '_type').inspect,
154
+ :counter_cache_column => reflection.counter_cache_column.to_s.inspect,
155
+ :join_table => reflection.options[:join_table].inspect,
156
+ :foreign_key => reflection_foreign_key.inspect
157
+ )
158
+ end
159
+
160
+ options
161
+ end
162
+ end
163
+
164
+ # Ensure that the belongs_to relationship exists. Will also test that the
165
+ # subject table has the association_id column.
166
+ #
167
+ # == Options
168
+ #
169
+ # * <tt>:class_name</tt> - the expected associted class name.
170
+ # * <tt>:foreign_key</tt> - the expected foreign key in the subject table.
171
+ # * <tt>:dependent</tt> - the expected dependent value for the association.
172
+ # * <tt>:readonly</tt> - checks wether readonly is true or false.
173
+ # * <tt>:validate</tt> - checks wether validate is true or false.
174
+ # * <tt>:autosave</tt> - checks wether autosave is true or false.
175
+ # * <tt>:counter_cache</tt> - the expected dependent value for the association.
176
+ # It also checks if the column actually exists in the table.
177
+ # * <tt>:polymorphic</tt> - if the association should be polymorphic or not.
178
+ # When true it also checks for the association_type column in the subject table.
179
+ #
180
+ # Plus all supported sql conditions options: :select, :conditions, :order,
181
+ # :limit, :offset, :include, :group, :having.
182
+ #
183
+ # == Examples
184
+ #
185
+ # should_belong_to :parent, :polymorphic => true
186
+ # it { should belong_to(:parent) }
187
+ #
188
+ def belong_to(*associations, &block)
189
+ AssociationMatcher.new(:belongs_to, *associations, &block).spec(self)
190
+ end
191
+
192
+ # Ensures that the has_and_belongs_to_many relationship exists, if the join
193
+ # table is in place and if the foreign_key column exists.
194
+ #
195
+ # == Options
196
+ #
197
+ # * <tt>:class_name</tt> - the expected associted class name.
198
+ # * <tt>:join_table</tt> - the expected join table name.
199
+ # * <tt>:foreign_key</tt> - the expected foreign key in the association table.
200
+ # * <tt>:uniq</tt> - checks wether uniq is true or false.
201
+ # * <tt>:readonly</tt> - checks wether readonly is true or false.
202
+ # * <tt>:validate</tt> - checks wether validate is true or false.
203
+ # * <tt>:autosave</tt> - checks wether autosave is true or false.
204
+ #
205
+ # Plus all supported sql conditions options: :select, :conditions, :order,
206
+ # :limit, :offset, :include, :group, :having.
207
+ #
208
+ # == Examples
209
+ #
210
+ # should_have_and_belong_to_many :posts, :cars
211
+ # it{ should have_and_belong_to_many :posts, :cars }
212
+ #
213
+ def have_and_belong_to_many(*associations, &block)
214
+ AssociationMatcher.new(:has_and_belongs_to_many, *associations, &block).spec(self)
215
+ end
216
+
217
+ # Ensures that the has_many relationship exists. Will also test that the
218
+ # associated table has the required columns. It works by default with
219
+ # polymorphic association (:as does not have to be supplied).
220
+ #
221
+ # == Options
222
+ #
223
+ # * <tt>:class_name</tt> - the expected associted class name.
224
+ # * <tt>:through</tt> - the expected join model which to perform the query.
225
+ # It also checks if the through table exists.
226
+ # * <tt>:source</tt> - the source of the through association.
227
+ # * <tt>:source_type</tt> - the source type of the through association.
228
+ # * <tt>:foreign_key</tt> - the expected foreign key in the associated table.
229
+ # When used with :through, it will check for the foreign key in the join table.
230
+ # * <tt>:dependent</tt> - the expected dependent value for the association.
231
+ # * <tt>:uniq</tt> - checks wether uniq is true or false.
232
+ # * <tt>:readonly</tt> - checks wether readonly is true or false.
233
+ # * <tt>:validate</tt> - checks wether validate is true or false.
234
+ # * <tt>:autosave</tt> - checks wether autosave is true or false.
235
+ #
236
+ # Plus all supported sql conditions options: :select, :conditions, :order,
237
+ # :limit, :offset, :include, :group, :having.
238
+ #
239
+ # == Examples
240
+ #
241
+ # should_have_many :friends
242
+ # should_have_many :enemies, :through => :friends
243
+ # should_have_many :enemies, :dependent => :destroy
244
+ #
245
+ # it{ should have_many(:friends) }
246
+ # it{ should have_many(:enemies, :through => :friends) }
247
+ # it{ should have_many(:enemies, :dependent => :destroy) }
248
+ #
249
+ def have_many(*associations, &block)
250
+ AssociationMatcher.new(:has_many, *associations, &block).spec(self)
251
+ end
252
+
253
+ # Ensures that the has_many relationship exists. Will also test that the
254
+ # associated table has the required columns. It works by default with
255
+ # polymorphic association (:as does not have to be supplied).
256
+ #
257
+ # == Options
258
+ #
259
+ # * <tt>:class_name</tt> - the expected associted class name.
260
+ # * <tt>:through</tt> - the expected join model which to perform the query.
261
+ # It also checks if the through table exists.
262
+ # * <tt>:source</tt> - the source of the through association.
263
+ # * <tt>:source_type</tt> - the source type of the through association.
264
+ # * <tt>:foreign_key</tt> - the expected foreign key in the associated table.
265
+ # When used with :through, it will check for the foreign key in the join table.
266
+ # * <tt>:dependent</tt> - the expected dependent value for the association.
267
+ # * <tt>:validate</tt> - checks wether validate is true or false.
268
+ # * <tt>:autosave</tt> - checks wether autosave is true or false.
269
+ #
270
+ # Plus all supported sql conditions options: :select, :conditions, :order,
271
+ # :limit, :offset, :include, :group, :having.
272
+ #
273
+ # == Examples
274
+ #
275
+ # should_have_one :universe
276
+ # it{ should have_one(:universe) }
277
+ #
278
+ def have_one(*associations, &block)
279
+ AssociationMatcher.new(:has_one, *associations, &block).spec(self)
280
+ end
281
+
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,68 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class HaveColumnMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
+ arguments :collection => :columns, :as => :column
6
+
7
+ optional :type, :default, :precision, :limit, :scale, :sql_type
8
+ optional :primary, :null, :default => true
9
+
10
+ collection_assertions :column_exists?, :options_match?
11
+
12
+ before_assert do
13
+ @options.each{|k,v| @options[k] = v.to_s}
14
+ end
15
+
16
+ protected
17
+
18
+ def column_exists?
19
+ !column_type.nil?
20
+ end
21
+
22
+ def options_match?
23
+ actual = get_column_options(column_type, @options.keys)
24
+ return actual == @options, :actual => actual.inspect
25
+ end
26
+
27
+ def column_type
28
+ subject_class.columns.detect {|c| c.name == @column.to_s }
29
+ end
30
+
31
+ def get_column_options(column, keys)
32
+ keys.inject({}) do |hash, key|
33
+ hash[key] = column.instance_variable_get("@#{key}").to_s
34
+ hash
35
+ end
36
+ end
37
+
38
+ def interpolation_options
39
+ { :options => @options.inspect }
40
+ end
41
+
42
+ end
43
+
44
+ # Ensures that a column of the database actually exists.
45
+ #
46
+ # == Options
47
+ #
48
+ # * All options available in migrations are available:
49
+ #
50
+ # :type, :default, :precision, :limit, :scale, :sql_type, :primary, :null
51
+ #
52
+ # == Examples
53
+ #
54
+ # should_have_column :name, :type => :string, :default => ''
55
+ #
56
+ # it { should have_column(:name, :type => :string) }
57
+ # it { should have_column(:name).type(:string) }
58
+ #
59
+ def have_column(*args, &block)
60
+ HaveColumnMatcher.new(*args, &block).spec(self)
61
+ end
62
+ alias :have_columns :have_column
63
+ alias :have_db_column :have_column
64
+ alias :have_db_columns :have_column
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,68 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class HaveDefaultScopeMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
+ arguments
6
+ assertions :options_match?
7
+
8
+ optionals :where, :having, :select, :group, :order, :limit, :offset, :joins, :includes, :lock, :readonly, :from
9
+
10
+ protected
11
+
12
+ def options_match?
13
+ default_scope.include?(@options)
14
+ end
15
+
16
+ def default_scope
17
+ @default_scope ||= if @subject
18
+ scopes = subject_class.default_scoping || []
19
+ scopes.map!{ |s| s[:find] }
20
+ else
21
+ []
22
+ end
23
+ end
24
+
25
+ def interpolation_options
26
+ { :options => @options.inspect, :actual => default_scope.inspect }
27
+ end
28
+
29
+ end
30
+
31
+ # Ensures that the model has a default scope with the given options.
32
+ #
33
+ # == Options
34
+ #
35
+ # All options that the default scope would pass on to find: :conditions,
36
+ # :include, :joins, :limit, :offset, :order, :select, :readonly, :group,
37
+ # :having, :from, :lock.
38
+ #
39
+ # == Examples
40
+ #
41
+ # it { should have_default_scope(:conditions => {:visible => true}) }
42
+ # it { should have_default_scope.conditions(:visible => true) }
43
+ #
44
+ # Passes for:
45
+ #
46
+ # default_scope :conditions => { :visible => true }
47
+ #
48
+ # If you set two different default scopes, you have to spec them
49
+ # separatedly. Given the scopes:
50
+ #
51
+ # default_scope :conditions => { :visible => true }
52
+ # default_scope :conditions => { :published => true }
53
+ #
54
+ # Then we have the matchers:
55
+ #
56
+ # should_have_default_scope :conditions => { :visible => true } # Passes
57
+ # should_have_default_scope :conditions => { :published => true } # Passes
58
+ #
59
+ # should_have_default_scope :conditions => { :published => true,
60
+ # :visible => true } # Fails
61
+ #
62
+ def have_default_scope(*args, &block)
63
+ HaveDefaultScopeMatcher.new(*args, &block).spec(self)
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,73 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class HaveIndexMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
+ arguments :collection => :columns, :as => :column
6
+
7
+ optional :table_name
8
+ optional :unique, :default => true
9
+
10
+ collection_assertions :index_exists?, :is_unique?
11
+
12
+ protected
13
+
14
+ def index_exists?
15
+ !matched_index.nil?
16
+ end
17
+
18
+ def is_unique?
19
+ return true unless @options.key?(:unique)
20
+ return @options[:unique] == matched_index.unique, :actual => matched_index.unique
21
+ end
22
+
23
+ def matched_index
24
+ columns = [@column].flatten.map(&:to_s)
25
+ indexes.detect { |ind| ind.columns == columns }
26
+ end
27
+
28
+ def indexes
29
+ @indexes ||= ::ActiveRecord::Base.connection.indexes(current_table_name)
30
+ end
31
+
32
+ def interpolation_options
33
+ @subject ? { :table_name => current_table_name } : {}
34
+ end
35
+
36
+ private
37
+
38
+ def current_table_name
39
+ @options[:table_name] || subject_class.table_name
40
+ end
41
+
42
+ end
43
+
44
+ # Ensures the database column has specified index.
45
+ #
46
+ # == Options
47
+ #
48
+ # * <tt>unique</tt> - when supplied, tests if the index is unique or not
49
+ # * <tt>table_name</tt> - when supplied, tests if the index is defined for the given table
50
+ #
51
+ # == Examples
52
+ #
53
+ # it { should have_index(:ssn).unique(true) }
54
+ # it { should have_index([:name, :email]).unique(true) }
55
+ #
56
+ # should_have_index :ssn, :unique => true, :limit => 9, :null => false
57
+ #
58
+ # should_have_index :ssn do |m|
59
+ # m.unique
60
+ # m.limit = 9
61
+ # m.null = false
62
+ # end
63
+ #
64
+ def have_index(*args, &block)
65
+ HaveIndexMatcher.new(*args, &block).spec(self)
66
+ end
67
+ alias :have_indices :have_index
68
+ alias :have_db_index :have_index
69
+ alias :have_db_indices :have_index
70
+
71
+ end
72
+ end
73
+ end