Home > Back-end >  What is the difference between these two form of .new code in ruby?
What is the difference between these two form of .new code in ruby?

Time:07-31

new to ruby on rails. have no idea on what's the difference on these two lines.

validation_code = ValidationCode.new { |v| v.email = params[:email], v.kind = 'sign_in', v.verify_code = code  }
validation_code = ValidationCode.new email: params[:email], kind: 'sign_in', verify_code: code

ask this is because I'm getting two different result.

def create
    code = SecureRandom.random_number.to_s[2..7]
    validation_code = ValidationCode.new { |v| v.email = params[:email], v.kind = 'sign_in', v.verify_code = code  }
    # validation_code = ValidationCode.new email: params[:email], kind: 'sign_in', verify_code: code
    if validation_code.save
        render status: 200
    else
        render json:{errors: validation_code.errors}, status: 400
    end
end

first written will return status 200, and second written will return status 400. Why?

Thanks

CodePudding user response:

So first, the first one is using the block form of ActiveRecord's initialize method, see more in the docs. There's no real difference to how each works, it's really just a style choice.

Your problem here is that the code you've included in your block isn't doing what you think it is because you're using commas to try to separate each statement. Ruby isn't seeing what you think it is, take a look at what v.email equals after that assignment. It will be an array of the email AND the other two elements!

CodePudding user response:

This is how the difference looks visually:

# With keyword arguments

validation_code = ValidationCode.new email: params[:email], kind: 'sign_in', verify_code: code

validation_code.email == params[:email]
# => true

validation_code.kind == 'sign_in'
# => true

validation_code.verify_code == code
# => true
# With block

validation_code = ValidationCode.new { |v| v.email = params[:email], v.kind = 'sign_in', v.verify_code = code  }

validation_code.email == [params[:email], 'sign_in', code].to_s
# => true

validation_code.kind == 'sign_in'
# => true

validation_code.verify_code == code
# => true

Because Ruby read it as

validation_code = ValidationCode.new { |v| v.email = [params[:email], v.kind = 'sign_in', v.verify_code = code]  }

Pay attention that Rails do not just assign an array value to email, but convert it to string

So in fact email is something like "[\"[email protected]\", \"sign_in\", 1234]"

If you want both lines to work the same, then the line with the block can be written as

validation_code = ValidationCode.new { |v| v.email = params[:email]; v.kind = 'sign_in'; v.verify_code = code  }

or better multi-line

validation_code =
  ValidationCode.new do |v|
    v.email = params[:email]
    v.kind = 'sign_in'
    v.verify_code = code
  end

CodePudding user response:

Both forms of new do the same thing (though your code does not) just in different ways.

model = Model.new(key1: value1, key2: value2) makes a new object, initializes it with the given values, and returns the new object.

model = Model.new { |m|
  m.key1 = value1
  m.key2 = value2
}

Makes a new object, and passes it to the block uninitialized. The block can now initialize the object in any way it pleases. Then the new, initialized object is returned.

The block form just offers more flexibility.


Why not just write this?

model = Model.new
model.key1 = value1
model.key2 = value2

If there is an after_initialize callback it will be called after new and before the object is initialized. That may cause problems because the object is not yet initialized.

But its really for create:

model = Model.create
model.key1 = value1
model.key2 = value2

create will call many callbacks on the uninitialized object, and it may violate database constraints such as not null.


Back to your code: the problem is you've tried to separate statements with commas in order to cram it into one line.

validation_code = ValidationCode.new { |v| 
  v.email = params[:email], v.kind = 'sign_in', v.verify_code = code
                          ^                   ^
}

v.kind and v.verify_code are fine, but v.email is an Array. You don't need the [] to make an Array. Ruby has parsed your code as:

v.email = [ params[:email], v.kind = 'sign_in', v.verify_code = code ]

Let's do the equivalent in IRB.

3.1.2 :001 > a = 4, b = 8, c = 12
 => [4, 8, 12] 
3.1.2 :002 > a
 => [4, 8, 12] 
3.1.2 :003 > b
 => 8 
3.1.2 :004 > c
 => 12 
3.1.2 :005 > 

Instead, use semicolons to separate statements.

validation_code = ValidationCode.new { |v| 
  v.email = params[:email]; v.kind = 'sign_in'; v.verify_code = code
}

Better yet, use newlines for clarity.

validation_code = ValidationCode.new { |v| 
  v.email = params[:email]
  v.kind = 'sign_in'
  v.verify_code = code
}
  • Related