Undefined method `stringify_keys' when calling update_attributes? - ruby-on-rails

I am getting this error: undefined method `stringify_keys' for :environ_gross_score:Symbol
when I attempt to create a new rating.
class Rating < ActiveRecord::Base
belongs_to :city
after_save :calculate_rating
def calculate_rating
#env = self.environ
self.city.environ_vote_count += 1
#c = self.city.environ_gross_score
#gross = #c += #env
self.city.update_attributes(:environ_gross_score, #gross )
#hold = self.city.environ_gross_score / self.city.environ_vote_count
self.city.update_attributes(:environ_rating, #hold)
end
end

update_attributes takes a single hash, not 2 parameters. Change the line to:
self.city.update_attributes(:environ_gross_score => #gross)
The error was happening because the method assumed that the first argument passed was a hash, which does (in Rails) respond to stringify_keys.

Related

Rspec - undefined method `expectation_fulfilled?' for nil:NilClass

Basically I am trying to mock object here:
if (object.present? && object.deleted_at.nil?)
My model is
class Relation
include Cequel::Record
...
column :deleted_at, :timestamp
end
When I tried the canonical way:
object = Object.new
object....assign necessary fields
object.deleted_at = nil
object = double
Rspec throws:
Failure/Error: method_name.to_s if ExpectationChain === chains.last unless
chains.last.expectation_fulfilled?
NoMethodError:
undefined method `expectation_fulfilled?' for nil:NilClass
When I tried the cheating way:
something = double
relation = double
expect(something).to receive(:nil).and_return(true)
expect(object).to receive(:present).and_return(true)
expect(object).to receive(:deleted_at).and_return(something)
Now (object.present? && object.deleted_at.nil?) evaluate to false
Huh? What should I do then?
EDIT: Tried the solution in the answer
object = instance_double(Relation, :present? => true, :deleted_at => nil)
I still get:
Failure/Error: method_name.to_s if ExpectationChain === chains.last unless chains.last.expectation_fulfilled?
NoMethodError:
undefined method `expectation_fulfilled?' for nil:NilClass
With the "cheating" way you are doing two things wrong.
1) you are expecting, not allowing
2) you are expecting to receive present when you are calling present? (question mark at the end)
Can you please try:
object = double(Relation)
allow(object).to receive(:present?).and_return(true)
allow(object).to receive(:deleted_at).and_return(nil)
Then:
object.present? => true
object.deleted_at.nil? => true
There is also a shorthand:
object = instance_double(Relation, :present? => true, deleted_at => nil)
I prefer the second solution (instance double). Then, you cannot mock a method that is not implemented by Relation

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 to stub a method that sets an object attribute in a Rails test?

I'm building a Rails spec test that has a Struct called temp_coverage, like this:
temp_coverage = Struct.new(:paydays) do
def calculate_costs
50
end
end
And in my spec, I call a method using the temp_coverage, but I'm getting an error since the code I am testing is doing the following:
temp_coverage.req_subscriber_election_amount = subscriber_election_amount
And I am getting an error:
NoMethodError: undefined method `req_subscriber_election_amount=' for < struct paydays=12 >
How can I stub out the setting of an attribute on a struct in my spec?
Is something like this you're looking for?
temp_coverage = double('temp_coverage', paydays: nil)
allow(temp_coverage).to receive(:calculate_costs).and_return(50)
allow(temp_coverage).to receive(:req_subscriber_election_amount=) do |argument|
temp_coverage.instance_variable_set(:#req_subscriber_election_amount, argument)
end
# Example:
temp_coverage.req_subscriber_election_amount = 123
puts temp_coverage.instance_variable_get(:#req_subscriber_election_amount)
# => 123
puts temp_coverage.paydays
# => nil
puts temp_coverage.calculate_costs
# => 50
I found a way to do this by using a named Struct. So once I named my Struct:
temp_coverage = Struct.new('CoverageClass', :paydays) do
def calculate_costs
50
end
end
I could then do the following:
Struct::CoverageClass.any_instance.stubs(:req_subscriber_election_amount).returns(25)

Undefined method 'id' for Class, but the console loads fine

I can access the shopping_list_products loaded in this API call from the console with no error.
I get a 500 on the API, however:
ActionView::Template::Error (undefined method `id' for #<Class:0x007f6cca7e1dd0>):
2015-01-26T23:51:07.608697+00:00 app[web.1]: 1: collection :#shopping_list_products
2015-01-26T23:51:07.608699+00:00 app[web.1]: 2: extends 'api/v1/shopping_list_products/_show'
index.json.rabl:
collection :#shopping_list_products
extends 'api/v1/shopping_list_products/_show'
show.json.rabl:
object :#shopping_list_product
extends 'api/v1/shopping_list_products/_show'
_show.json.rabl:
object :shopping_list_product
attribute(:id, :if => lambda { |m| m.id })
attributes :shopping_list_retailer_id, :product_id, ...
... more attributes
child :product, partial: 'api/v1/products/_show'
child :product_category, partial: 'api/v1/product_categories/_show'
node(:url) do |shopping_list_product|
api_v1_schedule_requisition_plan_shopping_list_shopping_list_retailer_shopping_list_product_path(#schedule, #shopping_list_retailer, shopping_list_product, format: :json)
end
EDIT: I removed the id attribute and then ran into the next error, "undefined method shopping_list_retailer_id for Class:". Why is this happening?
EDIT: Found out it's my code called from the controller.. if I return
#shopping_list_retailer.shopping_list_products
it works fine.
But I do this instead:
api :GET, '/schedule/:schedule_id/requisition_plan/shopping_list/shopping_list_retailers/:shopping_list_retailer_id/shopping_list_products', 'List all shopping list products for Shopping List for Requisition Plans for Schedule in the database'
param :query, String, desc: "Scoped_search style query string"
def index
#shopping_list_products = ShoppingListProductsIndexQuery.new(#shopping_list_retailer, params[:query]).shopping_list_products
end
class ShoppingListProductsIndexQuery
attr_reader :shopping_list_retailer, :query
def initialize(shopping_list_retailer, query)
#shopping_list_retailer = shopping_list_retailer
#query = query
end
def shopping_list_products
#shopping_list_retailer.shopping_list_products.ordered_by_product_category_type_and_product_category_name_and_product_name.search_for(query)
end
end
Still confused why undefined method id for Class is hit in the rabl view.
Your error message (undefined method 'id' for #<Class:0x007f6cca7e1dd0>) is saying the undefined method is on an instance of Class instead of an instance of ShoppingListProduct (or whatever your actual class name is).
This means the wrong thing is being rendered.
Probably because you are using:
object :#shopping_list_products
# and
object :#shopping_list_product
Instead of:
object #shopping_list_products
# and
object #shopping_list_product
Remove the :s.
Also, in your _show.json.rabl file, you really don't need
object :shopping_list_product
as this is ignored when the RABL template is being used as a partial (https://github.com/nesquena/rabl/wiki/Reusing-templates)

How do I pass an activerecord association as a string to a method?

Given the following associations:
User has_many Ultimate_Source
User has_many Budget_Source
How do I create the following method:
def foo(source)
user = User.find(1)
user.source.id
end
such that
foo(ultimate_sources)
returns:
user.ultimate_sources.id
thanks.
if source == :ultimate_sources or source == :budget_sources then the following should work.
user.send(source).id
if source is a string you can always convert it to a symbol using source.to_sym.
The send method takes a symbol representing the name of a method and sends the message to that object to execute that method and return what the method returns.
http://ruby-doc.org/core-2.1.2/Object.html#method-i-send
You can use the method .send:
def foo(source)
user = User.find(1)
user.send(source.to_s).id
end
Or the method .try (will not raise a NoMethodError if source is not a method of User):
def foo(source)
user = User.find(1)
user.try(source.to_s).id
end
But I really hope that source is not something coming from the user's input. What if I send delete as value of source variable? It would delete the user ...
I highly recommend you to limit the possible methods, in your case it could be something like this:
def foo(source)
user = User.find(1)
user.try("#{source.gsub('_sources', '')}_sources").try(:id)
end
This code version protects you to send destroy as the source value:
foo('ultimate_sources') # => user.ultimate_sources.id
foo('destroy') # => nil
foo('destroy_sources') # => nil
foo('budget') # => user.budget_sources.id
You could also put in a guard clause to be safe.
def foo(source)
return if (source != 'ultimate_sources') || (source != 'budget_sources')
user = User.find(1)
user.send(source).id
end

Resources