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
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>
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]
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
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.