maksar-remarkable_activerecord 4.0.0.alpha6

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