specstar-remarkable 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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