3

I come from a Python and Java background with only basic knowledge to CSS, HTML, Ruby and trying to learn web development using Ruby on Rails. I'm trying to follow the tutorial on Michael Hartl. I do not understand what arguments the post method in Listing 7.23 is doing.

require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
  end
end

From what I trace in the API, it takes in 2 non-optional arguments which are both Strings, but in Listing 7.23 there is a sudden hash syntax params: in the 2nd argument and this has confused me. Can anyone enlighten me?

John Gallagher
  • 6,208
  • 6
  • 40
  • 75
Prashin Jeevaganth
  • 1,223
  • 1
  • 18
  • 42

2 Answers2

5

I think you're looking at the wrong place, the link shows http.post. You want the IntegrationTest post.

From: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/integration.rb

def post(path, **args)
  process(:post, path, **args)
end

And:

def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil)
  request_encoder = RequestEncoder.encoder(as)
  headers ||= {}

  # rest
end

Edit: The double splat

Ruby 2.0 added the keyword arguments and the double splat. A single splat (*) is basically used when you have an unknown number of arguments, and it is passed as array.

def with_args(*args)
  p args
end

with_args(1,2,"a")
# [1, 2, "a"]

The double splat (**) acts like the *, but for keyword arguments:

def with_args(**args)
  with_keyword(args)
end

def with_keyword(some_key: nil, other_key: nil)
  p "some_key: #{some_key}, other_key: #{other_key}"
end

with_args(some_key: "some_value", other_key: "other_value")
# "some_key: some_value, other_key: other_value"
with_args(some_key: "some_value")
# "some_key: some_value, other_key: "

In ruby, you can call a method without () and pass a hash without {}, so

with_args({some_key: "some_value", other_key: "other_value"})

is like writing

with_args some_key: "some_value", other_key: "other_value")

See this answer: What does a double * (splat) operator do and https://medium.freecodecamp.org/rubys-splat-and-double-splat-operators-ceb753329a78

So...

When writing

post users_path, params: { user: { name:  "",
                                   email: "user@invalid",
                                   password:              "foo",
                                   password_confirmation: "bar" } }

Is calls to process

process(:post, users_path, params: { user: { name:  "",
                                   email: "user@invalid",
                                   password:              "foo",
                                   password_confirmation: "bar" } }

Meaning in process, params are the hash

{ user: { name:  "",
  email: "user@invalid",
  password:              "foo",
  password_confirmation: "bar" } }

It doesn't matter the other keyword args of process, the hash is all params, all the other keywords are nil

Hope it makes sense...

oren
  • 3,159
  • 18
  • 33
  • Hi, would you mind enlightening me how the method call to `process` works? I have explained some of my thought process in the answer above, but I can't fully grasp it from the tutorial's context. – Prashin Jeevaganth Dec 15 '18 at 15:16
  • @PrashinJeevaganth Sure, what I can. I edited the answer with the double splat (**) syntax and how the call is passed to process. – oren Dec 15 '18 at 15:46
  • Correct me if I'm wrong. By the assertion, we use the one without the error message, `User.count` is evaluated twice, once at the start, then a 2nd time after executing the code block, which is the `post` function that has some hash parameters as mentioned in this answer. `post` might return something due to `process`, but may also update the count, so we want the console to sound off "red" when there is actually a difference. Not quite sure what the assert's implementation is doing but that's my best bet. – Prashin Jeevaganth Dec 15 '18 at 15:59
  • @PrashinJeevaganth You are mostly correct, the `User.count` is evaluated twice. But the test is to check that the number stays the same, as it is a `post` request to create an **invalid** user. If the `post` request would update the count, it is a bug and the test should fail. If you will look at the implementation, you'll see that `assert_no_difference` just calls to `assert_difference` with 0. The implementation of is: `assert_difference`. https://apidock.com/rails/ActiveSupport/Testing/Assertions/assert_difference – oren Dec 15 '18 at 16:12
  • I'm actually confused by this: `post :create, article: invalid_attributes` in `assert_no_difference` , `:something` usually means that we have defined a `hash` beforehand and want to access the value of the corresponding key, but where do you find `:create` since it's a method and not some kind of key? The same confusion for `article`. I just interpreted it as what you mentioned just by reading the name, but I can't really reason with the code implementation. – Prashin Jeevaganth Dec 15 '18 at 16:29
  • @PrashinJeevaganth. I can see your confusion. I think you are confused with **other** `post` method. `assert_no_difference` takes a block as the third argument and just yields it when it needs to. The doc with the `post :create, article: invalid_attributes` is just an example for a block you can pass, not necessarily `IntegrationTest`. `:something` is just a symbol which this `post` method can work with. the `post :create` syntax is resemble to `Rspec` https://relishapp.com/rspec/rspec-rails/docs/controller-specs/controller-spec. Well maybe i'm wrong but this is how I see it... – oren Dec 15 '18 at 16:42
3

Ah! Great question. This line:

class UsersSignupTest < ActionDispatch::IntegrationTest

means that the class is inheriting from ActionDispatch::IntegrationTest.

ActionDispatch::IntegrationTest is a Rails class. You're looking at the docs for the Net::HTTP class, which is a Ruby class.

Here's the API docs for the ActionDispatch::IntegrationTest methods.

Getting mixed up between Ruby and Rails is very common at the start. Rails is the framework, Ruby is the language.

John Gallagher
  • 6,208
  • 6
  • 40
  • 75
  • Hi, thanks for the reply, actually I'm pretty confused by the number of API I have to look into(Rails,Ruby,HTTP,HTML,CSS). Usually I just get stumped at the tutorial and I don't know whether they are overriding an existing class method or they are implementing a new one from nowhere. – Prashin Jeevaganth Dec 15 '18 at 15:08
  • 1
    Oh, it's much more readable source than my answer, I just looked at github :) – oren Dec 15 '18 at 15:13
  • And I actually don't understand the API either as shown in the answer before yours, from my basic understanding I think `**args` in `post(path,**args)` does imply the need to pass in a Hash, and the same Hash is being passed into `process`, but then the problem arises when there are so many keyword arguments in `process` and I don't see how the key-value pairs that are contained in the Hash are mapped to the arguments. – Prashin Jeevaganth Dec 15 '18 at 15:13