why is ruby expecting wrong number of arguments - ruby-on-rails

I have a rails app where I am setting up a method before_save activeRecord callback, like this:
class GroupEvent < ApplicationRecord
enum status: [ :published, :draft ]
before_save :calculate_and_set_dates
def calculate_and_set_dates
missing_properties = []
#check for available or set attributes
puts "sss" + self.startDate.to_s
if !self.startDate
missing_properties<<"startDate"
end
if !self.duration
missing_properties<<"duration"
end
if !self.endDate
missing_properties<<"endDate"
end
binding.pry
if missing_properties.length<=1
set_missing_property(missing_properties[0])
else
set_errors_for(missing_properties)
end
end
private
def set_missing_property(missing_property)
case missing_property
when "startDate"
self.startDate = self.endDate - self.duration
when "duration"
self.duration = self.endDate - self.startDate
when "endDate"
self.endDate= self.startDate +self.duration
end
end
end
Note: this note the complete class so don't worry about set_errors_for methods implimentation.
Now when, I create a groupEvent using GroupEvent.create(name:"hackaton",description:"hecking my life away",startDate: DateTime.now, duration:10). At point where set_missing_property(missing_properties[0]) is called ,I get an error for wrong number of arguments:
ArgumentError: wrong number of arguments (given 1, expected 0) from /Users/haroonAzhar/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/activerecord-6.0.3.4/lib/active_record/attribute_methods/read.rb:15:in startDate'
As you can see that the method set_missing_property has 1 argument in definition, why is it expecting 0? What's even more confusing is that: when i pass no argument to the 'set_missing_property' method I get this error:
ArgumentError: wrong number of arguments (given 0, expected 1) from /Users/haroonAzhar/Desktop/develop/whitespectre/app/models/group_event.rb:29:in set_missing_property'
I don't know why it's looking at
/Users/haroonAzhar/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/activerecord-6.0.3.4/lib/active_record/attribute_methods/read.rb:15:in startDate'
when I give one parameter/argument but i checked out the file it was suggesting and the part it is referring to looks like this:
module ClassMethods # :nodoc:
private
def define_method_attribute(name)
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
generated_attribute_methods, name
) do |temp_method_name, attr_name_expr|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{temp_method_name}
name = #{attr_name_expr}
_read_attribute(name) { |n| missing_attribute(n, caller) }
end
RUBY
end
end
end
what is even look for at that place when the defined method is right in the class ? but the real question to which I need answer is why is it expecting wrong number of arguments and how do I fix it?
Thankyou in advance for your help, really appreciate it :D

Alright guys, I solved it(Maybe)
So at endDate case inside my set_missing_property, I changed it to:
when "endDate"
self.endDate = self.startDate + self.duration
I believe I was calling the self.startDate with a argument which doesnt expect any,so it was throwing that error.
Thanks for giving it a look to all the peeps that gave their time to this. really appreciate it.

Related

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 about attr_accessor, instance variables, local varibles

I'm so confused about that..
like this
class Box
attr_accessor :item ,:item2
def initialize(item2)
#item = []
#item2 = item2
end
def add(product)
item << product
end
def empty?
item.empty?
end
def increment(n=1)
item2 +=1
end
end
cart =Box.new(123)
cart.add(1)
puts cart.empty? #false
puts cart.item #1
in the 'add' and 'empty?' methods
I use local variable 'item' right?
why I can get the value from #items ??
and I try this
cart.item2 = 345
puts cart.item2 #345
puts cart.increment #'increment': undefined method `+' for nil:NilClass (NoMethodError)
now I can't get the value?
please fix my brain thx
First, read this answer, which is the most-upvoted Ruby post in StackOverflow history. It will help you understand attr_accessor and its cousins attr_reader and attr_writer.
Besides that, your code has many problems.
First, you should not name an Array with a singular variable name like item. Use a plural items to make its purpose clear.
Second, the name item2 is not good. For your attribute, use something descriptive like counter, and for the variable passed as an argument to initialize it, let's use something descriptive like initial_count.
Third, your increment method takes an optional argument but then ignores it. Wouldn't it be surprising if someone called box.increment(2) and the attribute was incremented by only 1? The intent of this method is to use counter += n instead of counter += 1.
Fourth, to set counter from within the class, we need to use self. So instead of counter += n, we have to do self.counter += n.
Finally, consider whether you want the attributes to be readable and writable from an outside source, or whether you want to reserve write privileges to the object itself. Because you have methods to add things to items and to increment counter, you probably want to conceal write privileges. I would use attr_reader publicly and attr_writer privately.
Incorporating these suggestions, here's the resulting code:
class Box
attr_reader :counter, :items
def initialize(initial_count)
#counter = initial_count
#items = []
end
def add(product)
items << product
end
def empty?
items.empty?
end
def increment(n = 1)
self.counter += n
end
private
attr_writer :counter, :items
end
Now you can do this, all of which makes sense, more or less:
>> cart = Box.new(123)
>> cart.increment(2)
>> cart.counter
#> 125
>> cart.add('A product')
>> cart.add('Another product')
>> cart.items
#> ["A product", "Another product"]
But if you try to set counter or items directly, you'll get an error:
>> cart.counter = 1
#> NoMethodError: private method `counter=' called for #<Box:0x007fc13e17dc50>

How is something like 30.seconds.ago implemented?

I found this question here.
And really curious to know the technical explanation of how something like 30.seconds.ago is implemented in Rails.
Method chaining? Numeric usage as per:
http://api.rubyonrails.org/classes/Numeric.html#method-i-seconds .
What else?
Here is the implementation of the seconds:
def seconds
ActiveSupport::Duration.new(self, [[:seconds, self]])
end
And, here is the implementation of the ago:
# Calculates a new Time or Date that is as far in the past
# as this Duration represents.
def ago(time = ::Time.current)
sum(-1, time)
end
And, here is the implementation of the sum method that's used inside the ago:
def sum(sign, time = ::Time.current) #:nodoc:
parts.inject(time) do |t,(type,number)|
if t.acts_like?(:time) || t.acts_like?(:date)
if type == :seconds
t.since(sign * number)
else
t.advance(type => sign * number)
end
else
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
end
end
end
To understand it fully, you should follow the method calls and look for their implementations in the Rails source code like I showed you just now.
One easy way to find a method definition inside Rails code base is to use source_location in your Rails console:
> 30.method(:seconds).source_location
# => ["/Users/rislam/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/core_ext/numeric/time.rb", 19]
> 30.seconds.method(:ago).source_location
# => ["/Users/rislam/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.3/lib/active_support/duration.rb", 108]

Is there a way to access method arguments in Ruby?

New to Ruby and ROR and loving it each day, so here is my question since I have not idea how to google it (and I have tried :) )
we have method
def foo(first_name, last_name, age, sex, is_plumber)
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{SOMETHING}"
end
So what I am looking for way to get all arguments passed to method, without listing each one. Since this is Ruby I assume there is a way :) if it was java I would just list them :)
Output would be:
Method has failed, here are all method arguments {"Mario", "Super", 40, true, true}
In Ruby 1.9.2 and later you can use the parameters method on a method to get the list of parameters for that method. This will return a list of pairs indicating the name of the parameter and whether it is required.
e.g.
If you do
def foo(x, y)
end
then
method(:foo).parameters # => [[:req, :x], [:req, :y]]
You can use the special variable __method__ to get the name of the current method. So within a method the names of its parameters can be obtained via
args = method(__method__).parameters.map { |arg| arg[1].to_s }
You could then display the name and value of each parameter with
logger.error "Method failed with " + args.map { |arg| "#{arg} = #{eval arg}" }.join(', ')
Note: since this answer was originally written, in current versions of Ruby eval can no longer be called with a symbol. To address this, an explicit to_s has been added when building the list of parameter names i.e. parameters.map { |arg| arg[1].to_s }
Since Ruby 2.1 you can use binding.local_variable_get to read value of any local variable, including method parameters (arguments). Thanks to that you can improve the accepted answer to avoid evil eval.
def foo(x, y)
method(__method__).parameters.map do |_, name|
binding.local_variable_get(name)
end
end
foo(1, 2) # => 1, 2
One way to handle this is:
def foo(*args)
first_name, last_name, age, sex, is_plumber = *args
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{args.inspect}"
end
This is an interesting question. Maybe using local_variables? But there must be a way other than using eval. I'm looking in Kernel doc
class Test
def method(first, last)
local_variables.each do |var|
puts eval var.to_s
end
end
end
Test.new().method("aaa", 1) # outputs "aaa", 1
If you need arguments as a Hash, and you don't want to pollute method's body with tricky extraction of parameters, use this:
def mymethod(firstarg, kw_arg1:, kw_arg2: :default)
args = MethodArguments.(binding) # All arguments are in `args` hash now
...
end
Just add this class to your project:
class MethodArguments
def self.call(ext_binding)
raise ArgumentError, "Binding expected, #{ext_binding.class.name} given" unless ext_binding.is_a?(Binding)
method_name = ext_binding.eval("__method__")
ext_binding.receiver.method(method_name).parameters.map do |_, name|
[name, ext_binding.local_variable_get(name)]
end.to_h
end
end
This may be helpful...
def foo(x, y)
args(binding)
end
def args(callers_binding)
callers_name = caller[0][/`.*'/][1..-2]
parameters = method(callers_name).parameters
parameters.map { |_, arg_name|
callers_binding.local_variable_get(arg_name)
}
end
You can define a constant such as:
ARGS_TO_HASH = "method(__method__).parameters.map { |arg| arg[1].to_s }.map { |arg| { arg.to_sym => eval(arg) } }.reduce Hash.new, :merge"
And use it in your code like:
args = eval(ARGS_TO_HASH)
another_method_that_takes_the_same_arguments(**args)
If the function is inside some class then you can do something like this:
class Car
def drive(speed)
end
end
car = Car.new
method = car.method(:drive)
p method.parameters #=> [[:req, :speed]]
If you would change the method signature, you can do something like this:
def foo(*args)
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{args}"
end
Or:
def foo(opts={})
# some code
# error happens here
logger.error "Method has failed, here are all method arguments #{opts.values}"
end
In this case, interpolated args or opts.values will be an array, but you can join if on comma. Cheers
It seems like what this question is trying to accomplish could be done with a gem I just released, https://github.com/ericbeland/exception_details. It will list local variables and vlaues (and instance variables) from rescued exceptions. Might be worth a look...
Before I go further, you're passing too many arguments into foo. It looks like all of those arguments are attributes on a Model, correct? You should really be passing the object itself. End of speech.
You could use a "splat" argument. It shoves everything into an array. It would look like:
def foo(*bar)
...
log.error "Error with arguments #{bar.joins(', ')}"
end

Why am I getting the error "undefined local variable or method `assigns'"?

I might be missing something basic here, but I'm stumped on this error:
model code:
class CachedStat < ActiveRecord::Base
def self.create_stats_days_ago(days_ago, human_id)
d = Date.today - days_ago.day
#prs = PageRequest.find(:all, :conditions => [ "owner_type = 'Human' and owner_id = ? and created_at = ?", human_id, d] )
end
end
spec code:
it "should create stats for the specified number of days in the past" do
CachedStat.create_stats_days_ago(1, Human.first.id)
assigns[:prs].should eql("foo")
end
The error is:
undefined local variable or method `assigns' for #<Spec::Rails::Example::ModelExampleGroup::Subclass_1:0x2fbac28>
I feel like I'm overlooking something obvious but it's invisible to me. Any suggestions?
Thanks very much!
-Jason
as neutrino said, assigns are only available in controllers/views specs, they're meaningless in a Model specs.
in your case example can look like
it "should create stats for the specified number of days in the past" do
CachedStat.create_stats_days_ago(1, Human.first.id).should eql("foo")
end
I could be wrong here, but assigns might be available only in controller specs.
Also, check your rspec version.

Resources