Undefined method 'id' for Class, but the console loads fine - ruby-on-rails

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)

Related

Rails params method: Why can it be accessed like a hash?

Viewing this code:
params[:id]
Params is considered to be a method. Correct me if I'm wrong there. But that's like reading from a hash. So, I'm currently confused.
If params is a method: How does the shown code-example work?
You are correct that params is a method, but here the params method returns an instance of ActionController::Parameters and we call hash accessor method #[] on it.
This is a common pattern in ruby to call methods on the returned object. Let's see it by a simple example:
def params
{
id: 101,
key: 'value',
foo: 'bar'
}
end
params[:id] # => 101
params[:foo] # => 'bar'
As you can see in the example, method params returns a hash object and we call hash accessor method #[] on the returned object.
Reference to rails params method: https://github.com/rails/rails/blob/5e1a039a1dd63ab70300a1340226eab690444cea/actionpack/lib/action_controller/metal/strong_parameters.rb#L1215-L1225
def params
#_params ||= begin
context = {
controller: self.class.name,
action: action_name,
request: request,
params: request.filtered_parameters
}
Parameters.new(request.parameters, context)
end
end
Note for ruby beginners: In ruby, we can call methods without parenthesis. So, above call is equivalent to params()[:id].
Those are known as square bracket accessors and you can add them to any object by implementing the [] and []= methods.
class Store
def initialize(**kwargs)
kwargs.each { |k,v| instance_variable_set("##{k}", v) }
end
def [](key)
instance_variable_get("##{key}")
end
def []=(key, value)
instance_variable_set("##{key}", value)
end
end
store = Store.new(foo: 1, bar: 2, baz: 3)
store[:foo] # 1
store[:foo] = 100
store[:foo] # 100
Also when you call params[:id] - the params method will be called first so you're calling [] on an instance of ActionController::Parameters just like in this simplefied example:
def foo
Store.new(bar: 1)
end
foo[:bar] # 1
Since parens are optional its equivilent to calling params()[:id].
In the context of a Controller, params is indeed a method. Let's say we have an OrganizationsController that is exposing the #index action in a restful endpoint. I will add a breakpoint using the pry gem so that we can better understand what params is:
class OrganizationsController < ApplicationController
def index
binding.pry # Runtime will stop here
render json: Organization.all
end
end
And let's visit the following URL:
http://localhost:3000/organizations.json?foo=bar
We can actually verify that params is a method by explicitly calling it with ():
> params()
=> #<ActionController::Parameters {"foo"=>"bar", "controller"=>"organizations", "action"=>"index", "format"=>"json"} permitted: false>
or by actually asking Ruby where that method is defined:
> method(:params).source_location
=> ["/home/myuser/.rvm/gems/ruby-3.0.2#myproject/gems/actionpack-6.1.4.1/lib/action_controller/metal/strong_parameters.rb", 1186]
The object returned by calling params is not a Hash, but an ActionController::Parameters instead:
> params.class
=> ActionController::Parameters
However, we can call the method :[] on it because it is actually defined in the ActionController::Parameters class (see code)
This makes it look like it's actually a Hash, but it is not, actually. For example, we cannot call the Hash method invert on params, as it is not defined:
> params.invert
NoMethodError: undefined method `invert' for #<ActionController::Parameters {"foo"=>"bar", "controller"=>"organizations", "action"=>"index", "format"=>"json"} permitted: false>

Argument Error : Wrong number of arguments

I am writing the following in rails console.
> class Hello
> def method
> d = Jobs.find_by_sql("select id, count(*) as TOTAL from table group by id having count>100")
> d.each do |m|
> puts m.account_id
> end
> end
> end
=> :method
> Hello.method
ArgumentError: wrong number of arguments (0 for 1)
I can't figure out what's wrong in this code. How can I solve this error.
your method name "method" is an existing method of the Object class, which is ultimately inherited by all classes in ruby.
You define an instance method with this name, which would be fine and would override the inherited instance method if it existed already. However, when you come to call it, you're calling it as a class method (because you're calling it on Hello, which is the class), so you're calling the existing "method" method, which is complaining about not getting any parameters.
Change your method to be called "foo" and then try to do Hello.foo. You'll get an "undefined method or variable" error because there's no foo class method.
Then do
hello = Hello.new
hello.foo
and it will work.
EDIT:
If you want it to actually be a class method, then you can do it via either of these ways:
class Hello
def self.method
d = Jobs.find_by_sql("select id, count(*) as TOTAL from table group by id having count>100")
d.each do |m|
puts m.account_id
end
end
end
end
or
class Hello
#class methods go in here
class << self
def method
d = Jobs.find_by_sql("select id, count(*) as TOTAL from table group by id having count>100")
d.each do |m|
puts m.account_id
end
end
end
end
end
As an aside, it's a convention, and generally a good idea, to use meaningful variable names. For example, if you have a variable which is a collection of Job objects, call it "jobs", not "d". Then anyone reading your code can easily remember what is held in that variable.
Using this principle, i would rewrite your code thus:
def output_job_account_ids
jobs = Jobs.find_by_sql("select id, count(*) as TOTAL from table group by id having count>100")
jobs.each do |job|
puts job.account_id
end
end
end
See how it's immediately much more obvious what is happening? I renamed the method name too: it's generally a good idea to have a method name describe what the method does.

Facing issue NoMethodError (undefined method `include' for "56":String):

I am facing issue while writing this code
def sim_state
sim_employees = SimEmployee.find(params[:id].include(:employees))
respond_to do |format|
format.js {
render :layout => false,
:locals => {
:sim_employees => sim_employee
}
}
end
end
and in my sim_states.js.erb
$('#simState').text('<%= sim_employee.employees.app_state%>');
So it gives me this error
NoMethodError (undefined method `include' for "56":String):
when i use it with includes then it gives
undefined method `includes'
Please guide me how to solve this.
The reason is simple
params[:id]
is return you a String value which is "56" and includes works with ActiveRecord::Relation and not string. So you are not able to fire the query that ways.
SimEmployee.find(params[:id])
This will return again a result and not relation. Try using
SimEmployee.where(id: params[:id]).includes(:employees).first
This should help you.
PS : Using includes for one record is same as firing two independent queries i.e.
#sim_employee = SimEmployee.find(params[:id])
#emplyess = #sim_employee.employees
There is a simple solution for this:
SimEmployee.includes(:employees).find params[:id]
SimEmployee.find(params[:id].include(:employees))
This line is causing the issue, you are calling include(:employees) on params[:id] which would be a number.
And you can't actually do .find(params[:id].include(:employees) because find method returns an instance of Model class, in this case, an instance of SimEmployee class, and you can't run method include on it.
Edit:
SimEmployee.includes(:employees).where()
You can pass a hash in where(). This works if your SimEmployee model has has_many relation to Employee model.

Creating new class to put into an Array in Ruby

I am coming from a C# background and trying to learn Ruby and Ruby on Rails. I have the following Car class - note the build_xml method I need in order to build XML in that syntax and then pass to a WebService
class Car
##array = Array.new
#this will allow us to get list of all instances of cars created if needed
def self.all_instances
##array
end
def initialize(id, model_number, engine_size, no_doors)
# Instance variables
#id = id
#model_number = model_number
#engine_size = engine_size
#no_doors = no_doors
##array << self
end
def build_car_xml
car = { 'abc:Id'=> #id, 'abc:ModelNo' => #model_number, 'abc:ES' => #engine_size, 'abc:ND' => #no_doors}
cars = {'abc:Car' => [car] }
end
end
In another class then I was using this as below:
car1 = Car.new('1', 18, 3.0, 4)
request = car1.build_car_xml
This works as expected and the request is formatted how I need and the webservice returns the results. I now want to expand this however so I can pass in an array of cars and produce the request XML - however I am struggling to get this part working.
So far I have been trying the following (for now I am ok with just the Id changing as it is the only parameter required to be unique):
car_array = []
(1..10).each do |i|
car_array << Car.new(i.to_s, 18, 3.0, 4)
end
Am I correct in saying that I would need to define a new build_car_xml method on my Car class that can take an array of cars and then build the xml so my request call would be something like:
request = Car.build_car_xml(car_array)
What i am unsure of is 1) - is this the correct way of doing things in Ruby and 2) how to construct the method so that it is Building the XML in the correct format in the way it was when I call it on the single object - i.e - I need the namespaces added before the actual value.
def build_car_xml(car_array)
#here is where I am unsure how to contruct this method
end
Possible solution ('abc:Car' is a wrong name, should be Cars if you want it to hold an array):
class Car
...
def self.build_cars_xml(cars)
{ 'abc:Car' => cars.map(&:build_car_xml) }
end
def build_car_xml
{ 'abc:Id'=> #id, 'abc:ModelNo' => #model_number, 'abc:ES' => #engine_size, 'abc:ND' => #no_doors }
end
end
cars =
(1..10).map do |i|
Car.new(i.to_s, 18, 3.0, 4)
end
Car.build_cars_xml(cars)
It doesn't meet your requirements as instance build_car_xml doesn't generate Car namespace, but for me it's some inconsistency. Your XML is actually a collection, even if it has just one element, instance method should not be responsible for collection. Car.build_cars_xml([Car.new(...)] looks more logical to me.

Some Instance Methods Are Undefined in acts_as_taggable

I am currently getting the following error on my post model which is under act_as_taggable_on for tags and channels.
undefined local variable or method `tag_list_on' for #
<ActiveRecord::Relation:0x007f9864675b48>
I seems that rails cannot detect the existence of tag_list_on or set_tag_list_on methods; however, it does detect the tagged_with method, whose source is located in the exact same module as the other files.
RubyMine can detect the existence of all of these methods fine btw.
Here is the section of code that I'm performing all of these operations on.
#posts = Post.tagged_with(params[:tags]).paginate(:page => params[:page]|| 1, :per_page => 20)
user_tags = #posts.tag_list_on(:tags)
custom_tags = user_tags - params[:tags]
#posts.set_tag_list_on(:customs, custom_tags)
#tags = #posts.tag_counts_on(:customs, :order => "count desc").limit(10)
#channels = #posts.tag_counts_on(:channels, :order => "count desc")
tagged_with is a class method of Post, added by the acts_as_taggable_on gem.
#posts in your code is an instance of ActiveRecord::Relation, not the Post class itself or any instance of it.
There is no tag_list_on instance method in ActiveRecord::Relation, hence the error.
tagged_with says it returns
...a scope of objects that are tagged with the specified tags.
tag_list_on and set_tag_list_on are instance methods of the Post class, added by the acts_as_taggable gem.
You need to call tag_list_on and set_tag_list_on for each instance of #posts
user_tags = []
#posts.each do |post|
user_tags = user_tags + post.tag_list_on(:tags)
end
custom_tags = user_tags.uniq - params[:tags]
# ...

Resources