Ruby - passing block inside the function parenthesis - ruby-on-rails

I have recently started to learn Ruby on Rails and it is really weird to get used to the syntax of Ruby.
I decided to go with all the parenthesis (that I know from other languages) that can be placed and I got stuck:
test "invalid signup information" do
get signup_path
assert_no_difference("User.count", {
user_params = { user: {
name: "",
email: "foo#invalid",
password: "foo",
password_confirmation: "bar"
}}
post(user_path, {params: user_params})
})
end
I want to pass a block into the assert_no_difference and somehow it is showing me an error during my tests. It started to show it after I places the definition of user_params. As far as I read some websites the syntax is OK, so what might be going wrong?

There's two general forms for passing in blocks. The long-form way is to use do ... end:
assert_no_difference('User.count') do
# ...
end
There's also the curly brace version:
assert_no_difference('User.count') {
# ...
}
Note that the curly brace style is generally reserved for single line operations, like this:
assert_no_difference('User.count') { post(...) }
For multi-line you generally want to use do...end since it's easier to spot. The When in Rome principle applies here, so you may need to shed some of your expectations in order to do things the Ruby way.
What you're doing wrong is passing in an argument that's presumed to be a Hash, but as it contains arbitrary code that's invalid. Unlike JavaScript the block is defined outside of the arguments to function call.
Cleaning up your code yields this:
test "invalid signup information" do
get signup_path
assert_no_difference("User.count") do
post(user_path,
params: {
user: {
name: "",
email: "foo#invalid",
password: "foo",
password_confirmation: "bar"
}
}
)
end
end
Note you can supply the arguments inline, plus any hash-style arguments specified as the last argument in a method call does not need its curly braces, they're strictly optional and usually best omitted.

Related

What is the difference between :id, id: and id in ruby?

I am trying to build a ruby on rails and graphQL app, and am working on a user update mutation. I have spent a long time trying to figure it out, and when I accidentally made a typo, it suddenly worked.
The below is the working migration:
module Mutations
class UpdateUser < BaseMutation
argument :id, Int
argument :first_name, String
argument :last_name, String
argument :username, String
argument :email, String
argument :password, String
field :user, Types::User
field :errors, [String], null: false
def resolve(id:, first_name:, last_name:, username:, email:, password:)
user = User.find(id)
user.update(first_name:, last_name:, username:, email:, password:)
{ user:, errors: [] }
rescue StandardError => e
{ user: nil, errors: [e.message] }
end
end
end
The thing I am confused about is when I define the arguments, they are colon first: eg :id or :first_name
When I pass them to the resolve method they only work if they have the colon after: eg id: or first_name:
When I pass the variables to the update method, they use the same syntax of colon after, for all variables other than ID. For some reason, when I used id: it was resolving to a string "id", and using colon first :id was returning an undefined error.
It is only when I accidentally deleted the colon, and tried id that it actually resolved to the passed through value.
My question for this, is why and how this is behaving this way? I have tried finding the answer in the docs, and reading other posts, but have been unable to find an answer.
Please someone help my brain get around this, coming from a PHP background, ruby is melting my brain.
It's going to take some time to get used to Ruby, coming from PHP, but it won't be too bad.
Essentially id is a variable, or object/model attribute when used like model_instance.id. In PHP this would be like $id or $object_instance->id.
When you see id: it is the key in a key-value pair, so it expects something (a value) after it (or assumes nil if nothing follows, often in method definitions using keyword arguments like your example). A typical use might be model_instance.update(id: 25) where you are essentially passing in a hash to the update method with id as the key and 25 as the value. The older way to write this in Ruby is with a "hash rocket" like so: model_instance.update(:id => 25).
More reading on Ruby hashes: https://www.rubyguides.com/2020/05/ruby-hash-methods
More reading on keyword arguments: https://www.rubyguides.com/2018/06/rubys-method-arguments
Now if you're paying attention that hash rocket now uses the 3rd type you're asking about. When you see a colon preceding a string like that it is called a "symbol" and it will take some time to get used to them but they are essentially strings in Ruby that are one character fewer to define (and immutable). Instead of using 'id' or "id" as a string, Ruby folks often like to use :id as a symbol and it will typically auto-convert to a string when needed. A good example might be an enumerator of sorts.
state = :ready
if state == :ready
state = :finished
else
state = :undefined
end
More reading on Ruby symbols: https://www.rubyguides.com/2018/02/ruby-symbols

What should I use in place of "assigns" when using RSpec with Rails 5+?

I'm currently testing the index action of one my controllers in the form of a request spec and want to make sure that the object collection is being passed through. So far I've consulted this post for guidance:
it "populates an array of contacts starting with the letter" do
smith = FactoryBot.create(:contact, lastname: 'Smith')
jones = FactoryBot.create(:contact, lastname: 'Jones')
get :index, letter: 'S'
expect(assigns(:contacts)).to match_array([smith])
end
Unfortunately the above example will thrown this error:
NoMethodError: assigns has been extracted to a gem. To continue using it, add `gem 'rails-controller-testing'` to your Gemfile.
I'd simple like to know what I would use in favor of assign in this case? I've looked high and low for an example for this new methodology but came up short.
Reference
rails-controller-testings - assigns
Just test the output of the controller instead of poking your fingers into the internals. Better yet don't use controller specs - use request or feature specs instead.
The output of the controller is the response object which contains headers and the response body.
So for example if you're testing an API in a request spec you could test the parsed json in the response body:
it "returns an array of contacts starting with the letter" do
smith = FactoryBot.create(:contact, lastname: 'Smith')
jones = FactoryBot.create(:contact, lastname: 'Jones')
get :index, letter: 'S'
last_names = parsed_response["contacts"].map { |c| c["lastname"] }
expect(last_names).to include 'Smith'
expect(last_names).to_not include 'Jones'
end

RSpec Rails - subject + assigns as one liner?

I am struggling with the following RSpec. Why does this works:
it 'GET articles#new creates new instance of Article' do
get :new
expect(assigns[:article]).to be_a(Article)
end
And neither of those do (found some examples with different brackets and that's why I decided to check both possibilities)
subject { get :new }
it { expect(assigns[:article]).to be_a(Article) }
it { expect(assigns(:article)).to be_a(Article) }
I am getting this error:
Failure/Error: it { expect(assigns(:article)).to be_a(Article) }
expected nil to be a kind of Article(id: integer, title: string, body: string, author_id: integer, created_at: datetime, updated_at: datetime)
# ./spec/controllers/articles_controller_spec.rb:35:in `block (4 levels) in <top (required)>'
I don't know how to retrieve "article" from the subject...
I have also tried some various combinations of "subject" and "assigns" inside it { ... }, but I couldn't get it working.
I'd rather keep it clean and store it in one line only :)
BTW: Do you have any other habits of writing Specs for controllers? (I already check for response 200)
According to the documentation, a controller spec :
allows you to simulate a single http request in each example, and then
specify expected outcomes such as:
instance variables assigned in the controller to be shared with the view
that's why you have to run the request (like get :new) inside the it block to return the correct output.
If you define the request as the subject of your spec (precision about subject here), you need to run the subject inside the it block :
subject { get :new }
it 'GET articles#new creates new instance of Article' do
subject
expect(assigns[:article]).to be_a(Article)
end
subject, like let is lazy-evaluated only when you are referencing it. You can use subject! instead, which will be evaluated before each example

Ruby on Rails Tutorial: Defining a hash with symbol keys

Regarding the exercises in Michael Hartl's RoR Tutorial in lesson 4.3.3 (Hashes & Symbols):
"Define a hash with symbol keys corresponding to name, email, and a “password digest”, and values equal to your name, your email address, and a random string of 16 lower-case letters."
I am hoping to get some input and/or alternative & 'better' solutions to this (or at least some criticism regarding my solution).
def my_hash
a = ('a'..'z').to_a.shuffle[0..15].join
b = { name: "John", email: "johndoe#gmail.com", password: a }
return b
end
puts my_hash
(Yes I realize this is a very simple exercise and apologize if it has been asked before.)
There are many 2 improvements could be made:
Use Array#sample to get random letters (it has an advantage: the letter might in fact repeat in the password, while shuffle[0..15] will return 16 distinct letters);
Avoid redundant local variables and especially return.
Here you go:
def my_hash
{
name: "John",
email: "johndoe#gmail.com",
password: ('a'..'z').to_a.sample(16).join
}
end
puts my_hash
Bonus:
I accidentaly found the third glitch in the original code. It should probably be:
def my_hash
{
name: "Brandon",
email: "brandon.elder#gmail.com",
password: ('a'..'z').to_a.sample(16).join
}
end
:)

Parameter passing and assert_difference

I am new to both Ruby and Rails. I don't understand why the following code (which uses Rails' [ActiveSupport::Testing.assert_difference]1 method) doesn't require a comma after the parameter 1. The code comes from Chapter 7 of the Rails Tutorial.
assert_difference 'User.count', 1 do
post_via_redirect users_path, ...
end
The signature for assert_difference is:
assert_difference(expression, difference = 1, message = nil, &block)
thus I would expect that a comma would be required between the difference parameter and the block parameter but that is obviously not the case.
Why is the comma not required?
Blocks aren't really parameters - what shows up in the method signature is that this method captures the block passed to it in a proc, but that is really an implementation detail that is leaked to the outside world. For example if you define a method like this
def foo(*args)
end
then blocks passed to this method don't end up in args.
However if you are passing a proc (or something that responds to to_proc), using the & argument prefix that you wish for this argument to be used as the method's block then you do need the comma.
my_proc = -> {post_via_redirect users_path}
assert_difference User.count, 1, &my_proc
Because you are passing the block using the special do |args| ... end/{ |args| ... } notation. If you pass the block as a normal argument you need the comma:
block = proc { post_via_redirect users_path, ... }
assert_difference 'User.count', 1, &block
Comma is not needed because this is how Ruby syntax works.
Block can be passed to method in 2 ways
1) Using do ... end
2) Using { ... }
def some_method(&block)
block.call
end
some_method { 2 + 2 }
#=> 4
some_method do
2 + 2
end
#=> 4
Try this examples in console and you will understand them.

Resources