diff options
Diffstat (limited to 'guides/source')
-rw-r--r-- | guides/source/active_record_migrations.md | 15 | ||||
-rw-r--r-- | guides/source/active_record_querying.md | 4 | ||||
-rw-r--r-- | guides/source/active_record_validations.md | 60 | ||||
-rw-r--r-- | guides/source/active_support_core_extensions.md | 64 | ||||
-rw-r--r-- | guides/source/autoloading_and_reloading_constants.md | 34 | ||||
-rw-r--r-- | guides/source/contributing_to_ruby_on_rails.md | 8 | ||||
-rw-r--r-- | guides/source/documents.yaml | 2 | ||||
-rw-r--r-- | guides/source/getting_started.md | 26 | ||||
-rw-r--r-- | guides/source/i18n.md | 5 | ||||
-rw-r--r-- | guides/source/layouts_and_rendering.md | 12 | ||||
-rw-r--r-- | guides/source/testing.md | 47 |
11 files changed, 160 insertions, 117 deletions
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index 97cabc1728..b8db21a989 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -479,7 +479,8 @@ Rails will generate a name for every foreign key starting with There is a `:name` option to specify a different name if needed. NOTE: Active Record only supports single column foreign keys. `execute` and -`structure.sql` are required to use composite foreign keys. +`structure.sql` are required to use composite foreign keys. See +[Schema Dumping and You](#schema-dumping-and-you). Removing a foreign key is easy as well: @@ -695,6 +696,10 @@ of `create_table` and `reversible`, replacing `create_table` by `drop_table`, and finally replacing `up` by `down` and vice-versa. This is all taken care of by `revert`. +NOTE: If you want to add check constraints like in the examples above, +you will have to use `structure.sql` as dump method. See +[Schema Dumping and You](#schema-dumping-and-you). + Running Migrations ------------------ @@ -943,10 +948,10 @@ that Active Record supports. This could be very useful if you were to distribute an application that is able to run against multiple databases. There is however a trade-off: `db/schema.rb` cannot express database specific -items such as triggers, or stored procedures. While in a migration you can -execute custom SQL statements, the schema dumper cannot reconstitute those -statements from the database. If you are using features like this, then you -should set the schema format to `:sql`. +items such as triggers, stored procedures or check constraints. While in a +migration you can execute custom SQL statements, the schema dumper cannot +reconstitute those statements from the database. If you are using features like +this, then you should set the schema format to `:sql`. Instead of using Active Record's schema dumper, the database's structure will be dumped using a tool specific to the database (via the `db:structure:dump` diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 434b308170..373a98bb85 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1134,7 +1134,7 @@ This would generate a query which contains a `LEFT OUTER JOIN` whereas the If there was no `where` condition, this would generate the normal set of two queries. NOTE: Using `where` like this will only work when you pass it a Hash. For -SQL-fragments you need use `references` to force joined tables: +SQL-fragments you need to use `references` to force joined tables: ```ruby Article.includes(:comments).where("comments.visible = true").references(:comments) @@ -1275,7 +1275,7 @@ User.active.where(state: 'finished') # SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished' ``` -If we do want the `last where clause` to win then `Relation#merge` can +If we do want the last `where` clause to win then `Relation#merge` can be used. ```ruby diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index c9af70934a..67cc6a4db3 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -227,8 +227,26 @@ end ``` We'll cover validation errors in greater depth in the [Working with Validation -Errors](#working-with-validation-errors) section. For now, let's turn to the -built-in validation helpers that Rails provides by default. +Errors](#working-with-validation-errors) section. + +### `errors.details` + +To check what validator type was used on invalid attribute, you can use +`errors.details[:attribute]`. It returns array of hashes where under `:error` + key you will find symbol of used validator. + +```ruby +class Person < ActiveRecord::Base + validates :name, presence: true +end + +>> person = Person.new +>> person.valid? +>> person.errors.details[:name] #=> [{error: :blank}] +``` + +Using `details` with custom validators are covered in the [Working with +Validation Errors](#working-with-validation-errors) section. Validation Helpers ------------------ @@ -452,7 +470,7 @@ point number. To specify that only integral numbers are allowed set If you set `:only_integer` to `true`, then it will use the ```ruby -/\A[+-]?\d+\Z/ +/\A[+-]?\d+\z/ ``` regular expression to validate the attribute's value. Otherwise, it will try to @@ -1074,6 +1092,42 @@ Another way to do this is using `[]=` setter # => ["Name cannot contain the characters !@#%*()_-+="] ``` +### `errors.details` + +You can add validator type to details hash when using `errors.add` method. + +```ruby + class Person < ActiveRecord::Base + def a_method_used_for_validation_purposes + errors.add(:name, :invalid_characters) + end + end + + person = Person.create(name: "!@#") + + person.errors.details[:name] + # => [{error: :invalid_characters}] +``` + +To improve error details to contain not allowed characters set, you can +pass additional options to `errors.add` method. + +```ruby + class Person < ActiveRecord::Base + def a_method_used_for_validation_purposes + errors.add(:name, :invalid_characters, not_allowed: "!@#%*()_-+=") + end + end + + person = Person.create(name: "!@#") + + person.errors.details[:name] + # => [{error: :invalid_characters, not_allowed: "!@#%*()_-+="}] +``` + +All built in Rails validators populate details hash with corresponding +validator types. + ### `errors[:base]` You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message. diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 080cc41e87..0fbd6ed7e1 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -467,7 +467,7 @@ C.new(0, 1).instance_variable_names # => ["@x", "@y"] NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. -### Silencing Warnings, Streams, and Exceptions +### Silencing Warnings and Exceptions The methods `silence_warnings` and `enable_warnings` change the value of `$VERBOSE` accordingly for the duration of their block, and reset it afterwards: @@ -475,26 +475,10 @@ The methods `silence_warnings` and `enable_warnings` change the value of `$VERBO silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } ``` -You can silence any stream while a block runs with `silence_stream`: - -```ruby -silence_stream(STDOUT) do - # STDOUT is silent here -end -``` - -The `quietly` method addresses the common use case where you want to silence STDOUT and STDERR, even in subprocesses: - -```ruby -quietly { system 'bundle install' } -``` - -For example, the railties test suite uses that one in a few places to prevent command messages from being echoed intermixed with the progress status. - Silencing exceptions is also possible with `suppress`. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is `kind_of?` any of the arguments, `suppress` captures it and returns silently. Otherwise the exception is reraised: ```ruby -# If the user is locked the increment is lost, no big deal. +# If the user is locked, the increment is lost, no big deal. suppress(ActiveRecord::StaleObjectError) do current_user.increment! :visits end @@ -3813,50 +3797,6 @@ WARNING. If the argument is an `IO` it needs to respond to `rewind` to be able t NOTE: Defined in `active_support/core_ext/marshal.rb`. -Extensions to `Logger` ----------------------- - -### `around_[level]` - -Takes two arguments, a `before_message` and `after_message` and calls the current level method on the `Logger` instance, passing in the `before_message`, then the specified message, then the `after_message`: - -```ruby -logger = Logger.new("log/development.log") -logger.around_info("before", "after") { |logger| logger.info("during") } -``` - -### `silence` - -Silences every log level lesser to the specified one for the duration of the given block. Log level orders are: debug, info, error and fatal. - -```ruby -logger = Logger.new("log/development.log") -logger.silence(Logger::INFO) do - logger.debug("In space, no one can hear you scream.") - logger.info("Scream all you want, small mailman!") -end -``` - -### `datetime_format=` - -Modifies the datetime format output by the formatter class associated with this logger. If the formatter class does not have a `datetime_format` method then this is ignored. - -```ruby -class Logger::FormatWithTime < Logger::Formatter - cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" } - - def self.call(severity, timestamp, progname, msg) - "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n" - end -end - -logger = Logger.new("log/development.log") -logger.formatter = Logger::FormatWithTime -logger.info("<- is the current time") -``` - -NOTE: Defined in `active_support/core_ext/logger.rb`. - Extensions to `NameError` ------------------------- diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md index f32714f893..f0ef03f0ce 100644 --- a/guides/source/autoloading_and_reloading_constants.md +++ b/guides/source/autoloading_and_reloading_constants.md @@ -80,7 +80,8 @@ end ``` The *nesting* at any given place is the collection of enclosing nested class and -module objects outwards. For example, in the previous example, the nesting at +module objects outwards. The nesting at any given place can be inspected with +`Module.nesting`. For example, in the previous example, the nesting at (1) is ```ruby @@ -113,6 +114,16 @@ certain nesting does not necessarily correlate with the namespaces at the spot. Even more, they are totally independent, take for instance ```ruby +module X + module Y + end +end + +module A + module B + end +end + module X::Y module A::B # (3) @@ -140,9 +151,10 @@ executed, and popped after it. * A singleton class opened with `class << object` gets pushed, and popped later. -* When any of the `*_eval` family of methods is called using a string argument, +* When `instance_eval` is called using a string argument, the singleton class of the receiver is pushed to the nesting of the eval'ed -code. +code. When `class_eval` or `module_eval` is called using a string argument, +the receiver is pushed to the nesting of the eval'ed code. * The nesting at the top-level of code interpreted by `Kernel#load` is empty unless the `load` call receives a true value as second argument, in which case @@ -153,8 +165,6 @@ the blocks that may be passed to `Class.new` and `Module.new` do not get the class or module being defined pushed to their nesting. That's one of the differences between defining classes and modules in one way or another. -The nesting at any given place can be inspected with `Module.nesting`. - ### Class and Module Definitions are Constant Assignments Let's suppose the following snippet creates a class (rather than reopening it): @@ -236,7 +246,7 @@ end ``` `Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If -all is good, the constant evaluates to an object that responds to `all`. +all is good, the constant is evaluated to an object that responds to `all`. That is why we talk about *constant* autoloading, Rails has the ability to load constants on the fly. @@ -314,7 +324,7 @@ relative: `::Billing::Invoice`. That would force `Billing` to be looked up only as a top-level constant. `Invoice` on the other hand is qualified by `Billing` and we are going to see -its resolution next. Let's call *parent* to that qualifying class or module +its resolution next. Let's define *parent* to be that qualifying class or module object, that is, `Billing` in the example above. The algorithm for qualified constants goes like this: @@ -685,12 +695,12 @@ creates an empty module and assigns it to the `Admin` constant on the fly. ### Generic Procedure Relative references are reported to be missing in the cref where they were hit, -and qualified references are reported to be missing in their parent. (See +and qualified references are reported to be missing in their parent (see [Resolution Algorithm for Relative Constants](#resolution-algorithm-for-relative-constants) at the beginning of this guide for the definition of *cref*, and [Resolution Algorithm for Qualified Constants](#resolution-algorithm-for-qualified-constants) for the definition of -*parent*.) +*parent*). The procedure to autoload constant `C` in an arbitrary situation is as follows: @@ -868,8 +878,8 @@ end ``` To resolve `User` Ruby checks `Admin` in the former case, but it does not in -the latter because it does not belong to the nesting. (See [Nesting](#nesting) -and [Resolution Algorithms](#resolution-algorithms).) +the latter because it does not belong to the nesting (see [Nesting](#nesting) +and [Resolution Algorithms](#resolution-algorithms)). Unfortunately Rails autoloading does not know the nesting in the spot where the constant was missing and so it is not able to act as Ruby would. In particular, @@ -1284,7 +1294,7 @@ c.user # NameError: uninitialized constant C::User ``` because it detects that a parent namespace already has the constant (see [Qualified -References](#autoloading-algorithms-qualified-references).) +References](#autoloading-algorithms-qualified-references)). As with pure Ruby, within the body of a direct descendant of `BasicObject` use always absolute constant paths: diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 7381521658..e06706d750 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -173,6 +173,14 @@ $ git checkout -b my_new_branch It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository. +### Bundle Update + +Update and install the required gems. + +```bash +$ bundle update +``` + ### Running an Application Against Your Local Branch In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch: diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index 67032a31f5..7ae3640937 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -33,7 +33,7 @@ url: active_record_querying.html description: This guide covers the database query interface provided by Active Record. - - name: Active Model basics + name: Active Model Basics url: active_model_basics.html description: This guide covers the use of model classes without Active Record. work_in_progress: true diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index fecd006a5f..31f2d2ed2f 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -193,6 +193,9 @@ following in the `blog` directory: $ bin/rails server ``` +TIP: If you are using Windows, you have to pass the scripts under the `bin` +folder directly to the Ruby interpreter e.g. `ruby bin\rails server`. + TIP: Compiling CoffeeScript and JavaScript asset compression requires you have a JavaScript runtime available on your system, in the absence of a runtime you will see an `execjs` error during asset compilation. @@ -1271,8 +1274,8 @@ bottom of the template: ```html+erb ... -<%= link_to 'Back', articles_path %> | -<%= link_to 'Edit', edit_article_path(@article) %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` And here's how our app looks so far: @@ -1485,6 +1488,9 @@ Without this file, the confirmation dialog box wouldn't appear.  +TIP: Learn more about jQuery Unobtrusive Adapter (jQuery UJS) on +[Working With Javascript in Rails](working_with_javascript_in_rails.html) guide. + Congratulations, you can now create, show, list, update and destroy articles. @@ -1681,8 +1687,8 @@ So first, we'll wire up the Article show template </p> <% end %> -<%= link_to 'Back', articles_path %> | -<%= link_to 'Edit', edit_article_path(@article) %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` This adds a form on the `Article` show page that creates a new comment by @@ -1762,8 +1768,8 @@ add that to the `app/views/articles/show.html.erb`. </p> <% end %> -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` Now you can add articles and comments to your blog and have them show up in the @@ -1828,8 +1834,8 @@ following: </p> <% end %> -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` This will now render the partial in `app/views/comments/_comment.html.erb` once @@ -1878,8 +1884,8 @@ Then you make the `app/views/articles/show.html.erb` look like the following: <h2>Add a comment:</h2> <%= render 'comments/form' %> -<%= link_to 'Edit Article', edit_article_path(@article) %> | -<%= link_to 'Back to Articles', articles_path %> +<%= link_to 'Edit', edit_article_path(@article) %> | +<%= link_to 'Back', articles_path %> ``` The second render just defines the partial template we want to render, diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 779526d733..fbee267975 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -530,7 +530,7 @@ Thus the following calls are equivalent: ```ruby I18n.t 'activerecord.errors.messages.record_invalid' -I18n.t 'errors.messages.record_invalid', scope: :active_record +I18n.t 'errors.messages.record_invalid', scope: :activerecord I18n.t :record_invalid, scope: 'activerecord.errors.messages' I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] ``` @@ -809,7 +809,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co | validation | with option | message | interpolation | | ------------ | ------------------------- | ------------------------- | ------------- | -| confirmation | - | :confirmation | - | +| confirmation | - | :confirmation | attribute | | acceptance | - | :accepted | - | | presence | - | :blank | - | | absence | - | :present | - | @@ -829,6 +829,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co | numericality | :equal_to | :equal_to | count | | numericality | :less_than | :less_than | count | | numericality | :less_than_or_equal_to | :less_than_or_equal_to | count | +| numericality | :other_than | :other_than | count | | numericality | :only_integer | :not_an_integer | - | | numericality | :odd | :odd | - | | numericality | :even | :even | - | diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index eb3c188d38..69d3f6e86c 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -316,12 +316,13 @@ NOTE: Unless overridden, your response returned from this render option will be #### Options for `render` -Calls to the `render` method generally accept four options: +Calls to the `render` method generally accept five options: * `:content_type` * `:layout` * `:location` * `:status` +* `:formats` ##### The `:content_type` Option @@ -430,6 +431,15 @@ Rails understands both numeric status codes and the corresponding symbols shown NOTE: If you try to render content along with a non-content status code (100-199, 204, 205 or 304), it will be dropped from the response. +##### The `:formats` Option + +Rails uses the format specified in request (or `:html` by default). You can change this adding the `:formats` option with a symbol or an array: + +```ruby +render formats: :xml +render formats: [:json, :xml] +``` + #### Finding Layouts To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions. diff --git a/guides/source/testing.md b/guides/source/testing.md index 94cfcf12b7..a083b3f981 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -480,21 +480,28 @@ In the `test_should_get_index` test, Rails simulates a request on the action cal The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments: -* The action of the controller you are requesting. This can be in the form of a string or a symbol. -* An optional hash of request parameters to pass into the action (eg. query string parameters or article variables). -* An optional hash of session variables to pass along with the request. -* An optional hash of flash values. +* The action of the controller you are requesting. + This can be in the form of a string or a symbol. + +* `params`: option with a hash of request parameters to pass into the action + (e.g. query string parameters or article variables). + +* `session`: option with a hash of session variables to pass along with the request. + +* `flash`: option with a hash of flash values. + +All the keyword arguments are optional. Example: Calling the `:show` action, passing an `id` of 12 as the `params` and setting a `user_id` of 5 in the session: ```ruby -get(:show, {'id' => "12"}, {'user_id' => 5}) +get(:show, params: { 'id' => "12" }, session: { 'user_id' => 5 }) ``` Another example: Calling the `:view` action, passing an `id` of 12 as the `params`, this time with no session, but with a flash message. ```ruby -get(:view, {'id' => '12'}, nil, {'message' => 'booya!'}) +get(:view, params: { 'id' => '12' }, flash: { 'message' => 'booya!' }) ``` NOTE: If you try running `test_should_create_article` test from `articles_controller_test.rb` it will fail on account of the newly added model level validation and rightly so. @@ -504,7 +511,7 @@ Let us modify `test_should_create_article` test in `articles_controller_test.rb` ```ruby test "should create article" do assert_difference('Article.count') do - post :create, article: {title: 'Some title'} + post :create, params: { article: { title: 'Some title' } } end assert_redirected_to article_path(assigns(:article)) @@ -534,7 +541,7 @@ NOTE: Functional tests do not verify whether the specified request type is accep ```ruby test "ajax request responds with no layout" do - xhr :get, :show, id: articles(:first).id + xhr :get, :show, params: { id: articles(:first).id } assert_template :index assert_template layout: nil @@ -638,7 +645,7 @@ Let's start by adding this assertion to our `test_should_create_article` test: ```ruby test "should create article" do assert_difference('Article.count') do - post :create, article: {title: 'Some title'} + post :create, params: { article: { title: 'Some title' } } end assert_redirected_to article_path(assigns(:article)) @@ -708,7 +715,7 @@ Let's write a test for the `:show` action: ```ruby test "should show article" do article = articles(:one) - get :show, id: article.id + get :show, params: { id: article.id } assert_response :success end ``` @@ -721,7 +728,7 @@ How about deleting an existing Article? test "should destroy article" do article = articles(:one) assert_difference('Article.count', -1) do - delete :destroy, id: article.id + delete :destroy, params: { id: article.id } end assert_redirected_to articles_path @@ -733,7 +740,7 @@ We can also add a test for updating an existing Article. ```ruby test "should update article" do article = articles(:one) - patch :update, id: article.id, article: {title: "updated"} + patch :update, params: { id: article.id, article: { title: "updated" } } assert_redirected_to article_path(assigns(:article)) end ``` @@ -759,20 +766,20 @@ class ArticlesControllerTest < ActionController::TestCase test "should show article" do # Reuse the @article instance variable from setup - get :show, id: @article.id + get :show, params: { id: @article.id } assert_response :success end test "should destroy article" do assert_difference('Article.count', -1) do - delete :destroy, id: @article.id + delete :destroy, params: { id: @article.id } end assert_redirected_to articles_path end test "should update article" do - patch :update, id: @article.id, article: {title: "updated"} + patch :update, params: { id: @article.id, article: { title: "updated" } } assert_redirected_to article_path(assigns(:article)) end end @@ -1026,7 +1033,8 @@ test "can create an article" do assert_response :success assert_template "articles/new", partial: "articles/_form" - post "/articles", article: {title: "can create", body: "article successfully."} + post "/articles", + params: { article: { title: "can create", body: "article successfully." } } assert_response :redirect follow_redirect! assert_response :success @@ -1042,7 +1050,8 @@ We start by calling the `:new` action on our Articles controller. This response After this we make a post request to the `:create` action of our Articles controller: ```ruby -post "/articles", article: {title: "can create", body: "article successfully."} +post "/articles", + params: { article: { title: "can create", body: "article successfully." } } assert_response :redirect follow_redirect! ``` @@ -1147,7 +1156,7 @@ require 'test_helper' class UserControllerTest < ActionController::TestCase test "invite friend" do assert_difference 'ActionMailer::Base.deliveries.size', +1 do - post :invite_friend, email: 'friend@example.com' + post :invite_friend, params: { email: 'friend@example.com' } end invite_email = ActionMailer::Base.deliveries.last @@ -1201,7 +1210,7 @@ within a model: ```ruby require 'test_helper' -class ProductTest < ActiveSupport::TestCase +class ProductTest < ActiveJob::TestCase test 'billing job scheduling' do assert_enqueued_with(job: BillingJob) do product.charge(account) |