I have the following classes in my rails 4 app:
class ReceiptDocument < ActiveRecord::Base
#foo
self.inheritance_column = :document_type
has_and_belongs_to_many :donations
protected
def self.inherited(subklass)
subklass.inherit_attributes(#foo)
end
def self.inherit_attributes(foo)
#foo = foo
end
end
class ReceiptRequest < ReceiptDocument; end
class ReceiptResult < ReceiptDocument; end
The class instance variable #foo is set once at class definition, and should have the same value in all subclasses. I added the inherited override so that value would be accessible from both ReceiptRequest and ReceiptResult.
However, now when I call ReceiptRequest.new, I get:
pry(main)> DonationReceiptRequest.new
NoMethodError: undefined method `[]' for nil:NilClass
from /gems/activerecord-4.1.5/lib/active_record/relation/delegation.rb:9:in `relation_delegate_class'
If I comment out that override, things return to normal. However, I can no longer share that value across all subclasses of ReceiptDocument.
Any insight would be greatly appreciated.
Finally got this sorted.
The error seemed to be pointing me at some behind the scenes work that ActiveRecord was doing, so I dug in a bit further.
It seems that setting self.inheritance_column most likely overrides the inherited method. Honestly, I'm not sure what work is being done that my override was stomping on. But redefining that method as below fixed the issue:
def self.inherited(subklass)
super # <--
subklass.inherit_attributes(#foo)
end
Related
I want to define methods dynamically using an array of strings.
Here is a simple piece of code that should achieve that.
class SomeClass
attr_accessor :my_array
def initialize(user, record)
#my_array=[]
end
my_array.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end
When I instantiate this class in the console, I get the following error
NoMethodError (undefined method `each' for nil:NilClass)
What is wrong with this code and how to make it work. any help highly appreciated :)
Edit 1:
What I ultimately want to achieve is to inherit from SomeClass and override my_array in the child class to dynamically define methods with its attributes like so
class OtherClass < SomeClass
my_array = %w[method1 method2 method3]
# Some mechanism to over write my_array.
end
And then use self.inherited to dynamically define methods in child class.
Is there a good way to achieve this?
In your code, you use an instance variable (#my_array) and an attr_accessor over it, and then try to access my_array from class level (that is, from the body of the class definition, outside of any methods). But instance variables only exist at instance level, so it is not available in the class scope.
One solution (the natural one, and the one which you would probably use in other languages) is to use a class variable: ##my_array. But class variables in ruby are a little problematic, so the best solution would be to make use of class instance variables, like that:
class SomeClass
class << self
attr_accessor :my_array
end
#my_array=[]
def initialize(user, record)
end
#my_array.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end
The syntax is a little tricky, so, if you look that up and it still doesn't makes sense, try just reading about scopes and using a regular class variable with ##.
Edit:
Ok, so, after your edit, it became more clear what you are trying to accomplish. A full working example is like follows:
class SomeClass
class << self
attr_accessor :my_array
end
#my_array=[]
def awesome_method
puts 'awesome'
end
def self.build!
#my_array.each do |element|
self.define_method("#{element}?".to_sym){ awesome_method }
end
end
end
class ChildClass < SomeClass
#my_array = %w[test little_test]
self.build!
end
child_instance = ChildClass.new
child_instance.test?
>> awesome
child_instance.little_test?
>> awesome
So, I've made some tweaks on SomeClass:
It does not need an initialize method
I tried to use the inherited hook for this problem. It won't ever work, because this hook is called as soon as "ChildClass < SomeClass" is written, and this must be before you can define something like #my_array = %w[test little_test]. So, I have added a self.build! method that must be called in the child instances so that they build their methods from my_array. This is inevitable, but I think it is also good, because it makes more explicit in the subclasses that you are doing something interesting there.
I think you want "define_method", not "alias_method".
awesome_method in passed in a block, which is ruby's way of doing functional programming.
With that done, ChildClass inherits from SomeClass, and it's instances have the dynamically created methods 'test?' and 'little_test?'.
You need to change my_array to class level accessible, in my case class constant.
class SomeClass
DYNAMIC_METHOD_NAMES = %w(method_a method_b method_C).freeze
def initialize(user, record)
end
DYNAMIC_METHOD_NAMES.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end
I am building a Rails 5.0 API and trying to have a print_it class method that runs as_json on an object. (I need a separate method to put complex logic into later)
Whenever I test it, it errors with:
NoMethodError (undefined method print_it for #<Class:0x007f7b7b092f20>):
In Model: project.rb
class Project < ApplicationRecord
def print_it
self.as_json
end
end
In controller: projects_controller.rb
class Api::V1::ProjectsController < Api::ApiController
def index
render json: Project.print_it
end
end
How can I use print_it on an object?
Project.print_it
is calling print_it on the class Project. But, you define print_it as an instance method, not a class method, here:
class Project < ApplicationRecord
def print_it
self.as_json
end
end
You probably want something more like:
class Api::V1::ProjectsController < Api::ApiController
def index
render json: #project.print_it
end
end
Naturally, you'll need to set #project.
To use print_it on an ActiveRecord_Relation called #projects, you could do something like:
#projects.map{|p| p.print_it}
You'll end up with an array.
But that might be expensive, depending on the number of projects and the nature of print_it.
How can I use print_it on an object?
You are 'using' (calling) print_it on an object. Project is an object. Just like #project is an object. You just happen to be calling print_it on an object that doesn't have print_it defined (thus the undefined method error).
I will also note that Jörg W Mittag wishes to say:
I am one of those Ruby Purists who likes to point out that there is no such thing as a class method in Ruby. I am perfectly fine, though, with using the term class method colloquially, as long as it is fully understood by all parties that it is a colloquial usage. In other words, if you know that there is no such thing as a class method and that the term "class method" is just short for "instance method of the singleton class of an object that is an instance of Class", then there is no problem. But otherwise, I have only seen it obstruct understanding.
Let it be fully understood by all parties that the term class method is used above in its colloquial sense.
I have an instance method that has logic I want to use in a query. My attempts have not worked. I'd rather not duplicate the logic inside is_thing when building my where clause.
class Foo < ActiveRecord::Base
def is_thing?
#... some logic
end
end
I tried
Foo.where(is_thing?)
and got
NoMethodError: undefined method `is_thing?' for main:Object
The Approach I would recommend
I do believe that method is not good practice (to chain a where query). It will only add unnecessary complexity.
The better approach is using scope
scope :is_thing?, -> { where(thing: true) }
and call it
Foo.is_thing?.where()
The Why
The reason it is returned
NoMethodError: undefined method `is_thing?' for main:Object
Is because is_thing? is instance variable of Foo
Instance variable can be called on an instance of the class. And not availabe on main object.
You, however could do
Foo.where(Foo.new.is_thing?)
It is posible to use that if you convert is_thing? to class method. e.g
def self.is_thing?
# code
end
and use it this way
Foo.where(Foo.is_thing?)
try this:
class Foo < ActiveRecord::Base
def is_thing?
#... some logic
end
def things
Foo.where('thing_check = ?', self.is_thing?)
end
end
create another instance method to wrap your where query. Then access it as Foo.last.things
I am trying to DRY my code by implementing modules. However, I have constants stored in models (not the module) that I am trying to access with self.class.
Here are (I hope) the relevant snippets:
module Conversion
def constant(name_str)
self.class.const_get(name_str.upcase)
end
end
module DarkElixir
def dark_elixir(th_level)
structure.map { |name_str| structure_dark_elixir(name_str, th_level) if constant(name_str)[0][:dark_elixir_cost] }.compact.reduce(:+)
end
end
class Army < ActiveRecord::Base
include Conversion, DarkElixir
TH_LEVEL = [...]
end
def structure_dark_elixir(name_str, th_level)
name_sym = name_str.to_sym
Array(0..send(name_sym)).map { |level| constant(name_str)[level][:dark_elixir_cost] }.reduce(:+) * TH_LEVEL[th_level][sym_qty(name)]
end
When I place the structure_dark_elixir method inside the DarkElixir module, I get an error, "uninitialized constant DarkElixir::TH_LEVEL"
While if I place it inside the Army class, it finds the appropriate constant.
I believe it is because I am not scoping the self.constant_get correctly. I would like to keep the method in question in the module as other models need to run the method referencing their own TH_LEVEL constants.
How might I accomplish this?
Why not just use class methods?
module DarkElixir
def dark_elixir(th_level)
# simplified example
th_level * self.class.my_th_level
end
end
class Army < ActiveRecord::Base
include DarkElixir
def self.my_th_level
5
end
end
Ugh. Method in question uses two constants. It was the second constant that was tripping up, not the first. Added "self.class::" prior to the second constant--back in business.
def structure_dark_elixir(name_str, th_lvl)
name_sym = name_str.to_sym
Array(0..send(name_sym)).map { |level| constant(name_str)[level][:dark_elixir_cost] }.reduce(:+) * self.class::TH_LEVEL[th_lvl][sym_qty(name_str)]
end
I user namespace to create some module in Rails. It works fine in controllers, models, but something is wrong with presenters which are in a presenters path.
This is one of presenters, without namespace:
class MainPresenter < Struct.new(:main, :current_user)
extend Ext::CollectionPresenter
def as_json
{
something: SomeNamespace::SomePresenter(main.something)
}
end
end
And this is the presenter in presenters/some_namespace/some_presenter.rb
class SomeNamespace::SomePresenter < Struct.new(:something, :options)
extend Ext::CollectionPresenter
def as_json
# some hash here
end
end
I get undefined method 'SomePresenter' for SomeNamespace:Module error. What could be the problem.
SomePresenter is a class and you are using it as a method. Hence, the error.
Use it as below :
something: SomeNamespace::SomePresenter.new(main.something)