Strange behavior for method default parameters initialization - ruby-on-rails

When I am trying to initialize this it is working perfectly fine
class Abc
def initialize(parent_id, user = nil, permission)
end
end
Abc.new(1 ,2, "some_permission")
But when I am doing this
class Abc
def initialize(parent_id, user = nil, permission, g_data = nil)
end
end
Abc.new(1 ,2, "some_permission", 4)
I am getting syntax error syntax error, unexpected '=', expecting ')'
This is a strange behavior why it is not taking two arguments default as nil

According to ruby documentation the default values should be grouped.
The default value does not need to appear first, but arguments with
defaults must be grouped together.
So according to this rule your method arguments should be listed like following:
class Abc
def initialize(parent_id, user = nil,g_data = nil,permission)
end
# OR
def initialize(parent_id, permission,user = nil,g_data = nil)
end
end

Related

Fetch optional parameters not working in rails

I am sending optional paramters to a method but they are not received. Using binding.pry I have checked but the link parameter is not received but id parameter is received in the send_email method. It is always returned as nil. Please help find the problem where I am going wrong
class EmailsController < MyAccountController
def send_emails
#user = current_user
id = #user.id
HiringMailer.send_email(id, link: true).deliver_now
end
end
class HiringMailer < ApplicationMailer
def send_email(id, joining = false, options={})
link = options[:link]
binding.pry_remote
#user = User.find(id)
#joining_user = joining
to = (['abc#yah.com', 'adx#yah.com']).flatten
mail to: to, subject: "Joining Date"
end
end
Update 1
HiringMailer.send_email(id, link: true).deliver_now
def send_email(id, joining = false, , **options)
binding.pry_remote
end
The link: true argument is getting swallowed up by the joining variable.
Let me explain. Here's the method signature:
def send_email(id, joining = false, options={})
Now, if you call that method with: send_email(123, link: true), then we end up with:
id = 123
joining = {link: true}
options = {}
To prevent this unwanted affect, you need to explicitly pass all three variables: send_email(123, false, link: true).
...But wait, there's an even better way! Use keyword arguments instead. You can define the method like this:
def send_email(id, joining: false, **options)
And call it exactly like you were doing before:
send_email(123, link: true)
The only minor difference (which is frankly a clear improvement) is that you'll need to invoke the method slightly differently if you want to set joining = true:
# Before:
send_email(123, true, link: true)
# After:
send_email(123, joining: true, link: true)

How to cause manually ActiveRecord RecordInvalid in Rails

Entry: I have attribute class that derive from one of the ActiveRecord types and implement method cast(value) which transforms provided value to derived Active Record type. In our case we perform transformation only when the provided value is a String otherwise the default integer casting is performed. Private method to_minutes converts formatted time to an integer representing spent minutes. I assumed that 1d = 8h = 480m. E.g. result of to_minutes('1d 1h 1m') = 541. (I used this resource Custom Attributes in Ruby on Rails 5 ActiveRecord
If the string that came has no numbers, I need to return a validation error, and set this error to the #card.errors. How should I do it? I try:
if time.scan(/\d/).empty?
raise ActiveRecord::RecordInvalid.new(InvalidRecord.new)
end
But it does not work, I get an error
NameError in CardsController#update
uninitialized constant CardDuration::Type::InvalidRecord
Extracted source (around line #15):
I create my own attribute for integer type:
class CardDuration
class Type < ActiveRecord::Type::Value
def cast(value)
if value.is_a?(String)
to_seconds(value)
else
super
end
end
private
def to_seconds(time)
if time.scan(/\d/).empty?
return raise ActiveRecord::RecordInvalid.new, { errors: {message: 'Duration is too short (minimum is 1 number)'} }
end
time_sum = 0
time.split(' ').each do |time_part|
value = time_part.to_i
type = time_part[-1,1]
case type
when 'm'
value
when 'h'
value *= 60
when 'd'
value *= 8*60
else
value
end
time_sum += value
end
time_sum
end
end
end
and inside model:
class Card < ApplicationRecord
validates :duration, length: { within: 0..14880 }
attribute :duration, CardDuration::Type.new
end
Also validation doesn't work, and I do not understand why.
Thanks)
Inside controller this field can only be updated, so I need set the error to the #card.errors:
class CardsController < ApplicationController
def update
if #card.update(card_params)
flash[:success] = "Card was successfully updated."
else
flash[:error] = #card.errors.full_messages.join("\n")
render status: 422
end
rescue ActiveRecord::RecordInvalid => e
return e.record
end
end
in ActiveRecord::RecordInvalid.new(...) you need to pass structure, which have method 'errors' documentation.
try to raise ActiveRecord::RecordInvalid.new(self.new)
or write or own class, with method errors, which will be handle your exeptions
The only problem that I can see in your code is the way you're raising the invalid record error.
The correct way to do that would be-
raise ActiveRecord::RecordInvalid

wrong number of arguments (given 4, expected 0..1) after upgrading to Rails 6

I just upgraded from Rails 5.2 to Rails 6 and I'm facing an issue in one class.
class Calculator < ApplicationRecord
def initialize(obj, user_id, calc_type, will_id = nil )
#inheritors = obj
super_obj = obj.clone.merge!(user_id: user_id, type: calc_type, will_id: will_id)
super(super_obj)
#shares = {}
#remains = RationalWithArgumentStore(0)
#stop_residual_shares = false
#special_case_apply = false
#rules = {}
#authorities = {}
end
end
and I'm creating new instance of the class like the following
calc = Calculator.new(obj, user_id, calc_type, nil)
It through an error saying:
wrong number of arguments (given 4, expected 0..1)
I spent two days searching and trying to fix the issue without any luck.
Your help is highly appreciated
This is happening because you are redefining initialize for a class that inherits from ApplicationRecord. It throws an error here. If you were to do the following it would work.
class Calculator
def initialize(obj, user_id, calc_type, will_id = nil)
#obj = obj
#user_id = user_id
#calc_type = calc_type
#will_id = will_id
end
end
Note that redefining initialize in this way is not recommended (see docs here), so if you can I would look into callbacks and see if you can accomplish your goal with that.
Hope that helps.
(Rails 6.x) One of my models had include Rails.application.routes.url_helpers.
If you have the same problem, remove it and manage your url methods by calling it directly as:
def my_link_function
# ... my code
my_link = Rails.application.routes.url_helpers.url_for([my_params]))
# ... other code
end

ruby: mass initializing instance variables

Is there an easy way to bulk assign instance variables
def initialize(title: nil, label_left: nil, label_right: nil, color_set: nil)
#title = title
#label_left = label_left
#label_right = label_right
#color_set = color_set
end
Can I loop/iterate through the initialize arguments and assign automatically?
If you want specific variables, then not really. Using reflection here, even if it was available, would be trouble.
What you've got here is the most trivial case. Often you'll see code that looks more like this:
def initialize(title: nil, label: nil, label_width: nil, color_set: nil)
#title = title.to_s
#label = label_left.to_s
#label_width = label_width.to_i
#color_set = MyRGBConverter.to_rgb(color_set)
end
The initializer is a place where you can do any necessary conversion and validation. If some of those arguments are required to be certain values you'll have tests for that, raise an exception on an error and so forth. The repetitive code you have here in the minimal case often gets expanded and augmented on so there's no general purpose solution possible.
That leads to code like this:
def initialize(title: nil, label: nil, label_width: nil, color_set: nil)
#title = title.to_s
unless (#title.match(/\S/))
raise "Title not specified"
end
#label = label_left.to_s
unless (#label.match(/\A\w+\z/))
raise "Invalid label #{#label.inspect}"
end
#label_width = label_width.to_i
if (#label_width <= 0)
raise "Label width must be > 0, #{#label_width} specified."
end
#color_set = MyRGBConverter.to_rgb(color_set)
end
If you really don't care about which arguments you're taking in then you can do this in Ruby 2.3 with the new keyword-arguments specifier **:
def initialize(**options)
options.each do |key, value|
instance_variable_set("##{key}", value)
end
end
Note that's potentially dangerous if those values are user supplied, so you might want to white-list them somehow:
VALID_OPTIONS = [ :title, :label_left, :label_right, :color_set ]
def initialize(**options)
options.each do |key, value|
raise "Unknown option #{key.inspect}" unless (VALID_OPTIONS.include?(key))
instance_variable_set("##{key}", value)
end
end
Where you're seeing a lot of these arguments being passed in on a regular basis you might want to evaluate if creating some kind of struct-like object and passing that through might be a better plan. It depends on how your code works.
#tadman provided already an excellent answer to this, but here is one more: If you are willing to dispense with named parameters, you could do it like this:
def initialize(*args)
#title, #label_left, #label_right, #color_set, *nada = args
fail "Too many arguments" unless nada.empty?
end
[UPDATE: Fixed the code, according to the comment given by #sawa].

Spree error when using decorator with the original code

Need a little help over here :-)
I'm trying to extend the Order class using a decorator, but I get an error back, even when I use the exactly same code from source. For example:
order_decorator.rb (the method is exactly like the source, I'm just using a decorator)
Spree::Order.class_eval do
def update_from_params(params, permitted_params, request_env = {})
success = false
#updating_params = params
run_callbacks :updating_from_params do
attributes = #updating_params[:order] ? #updating_params[:order].permit(permitted_params).delete_if { |k,v| v.nil? } : {}
# Set existing card after setting permitted parameters because
# rails would slice parameters containg ruby objects, apparently
existing_card_id = #updating_params[:order] ? #updating_params[:order][:existing_card] : nil
if existing_card_id.present?
credit_card = CreditCard.find existing_card_id
if credit_card.user_id != self.user_id || credit_card.user_id.blank?
raise Core::GatewayError.new Spree.t(:invalid_credit_card)
end
credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
attributes[:payments_attributes].first[:source] = credit_card
attributes[:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
attributes[:payments_attributes].first.delete :source_attributes
end
if attributes[:payments_attributes]
attributes[:payments_attributes].first[:request_env] = request_env
end
success = self.update_attributes(attributes)
set_shipments_cost if self.shipments.any?
end
#updating_params = nil
success
end
end
When I run this code, spree never finds #updating_params[:order][:existing_card], even when I select an existing card. Because of that, I can never complete the transaction using a pre-existent card and bogus gateway(gives me empty blanks errors instead).
I tried to bind the method in order_decorator.rb using pry and noticed that the [:existing_card] is actuality at #updating_params' level and not at #updating_params[:order]'s level.
When I delete the decorator, the original code just works fine.
Could somebody explain to me what is wrong with my code?
Thanks,
The method you want to redefine is not really the method of the Order class. It is the method that are mixed by Checkout module within the Order class.
You can see it here: https://github.com/spree/spree/blob/master/core/app/models/spree/order/checkout.rb
Try to do what you want this way:
Create file app/models/spree/order/checkout.rb with code
Spree::Order::Checkout.class_eval do
def self.included(klass)
super
klass.class_eval do
def update_from_params(params, permitted_params, request_env = {})
...
...
...
end
end
end
end

Resources