I am modelling a report as a class and each column value as an accessor. Each value in the report needs to be accessed from the database. But this makes the class look quite fat and RubyMine warns me of too many methods in class.
class Report
attr_accessor :name, :col1, :col2, :col3 .... :col15
def col1
db.find({x: 1})['some_var']
end
def col2
db.find({y: 4})['some_other_var']
end
and so forth for each attribute...
end
Since each getter is essentially single line that makes call to database, is there a simpler way to declare these vars without being in a method?
I don't want to set these in the initialize method as these reports will be subclassed and child reports will not have all/some of these attributes.
You can use meta-programming to create attr_accessor like methods on the fly.
For Example:
class Report
def initialize(attributes)
attributes.each do |attribute|
define_singleton_method :"#{attribute}" do |hash_param, string_param|
db.find(hash_param)[string_param]
end
end
end
end
Then you can create new report object and pass attribute names as follow:
r = Report.new(["n","m"])
Now you can call n and m methods on the r object
r.m({val1: "val1"}, "val2")
r.n({val2: "val1"}, "val2")
#Rahul based on answer to my question, the only advice I can give here then is to use best OOP design principles. Subclass and modularize where possible as well as using ruby meta-programming. If you need the methods, they have to be written somewhere. But if you only need getters, consider attr_reader instead, unless you'll need setters too.
If you can get the column names you can use dynamic method definition with something like this assuming db is magically defined somewhere you have not made clear, we'll assume it's a connection to the database.
class Report
db.column_names.each do |col|
define_method(col.to_sym) { db.find(options={}) }
end
end
If you just want RubyMine to stop nagging you, I assume it's using rubocop and you can see this post for how to override rules.
https://www.jetbrains.com/help/ruby/2017.1/rubocop.html
or
https://github.com/rubocop-hq/rubocop
Related
I am a beginner in ruby.
I've tried to run this code and it shows run time error.
What's wrong with this code?
class Calc
attr_accessor :val1, :val2
def initialize (val1,val2)
#val1=val1
#val2=val2
end
end
a=Calc.new(2,3)
a.add_two_numbers(3)
def add_two_numbers(v3)
return #val1+#val2+v3
end
The method add_two_numbers is not defined on the class Calc, however you are using it as if it is. This is the problem.
I would presume you got a NoMethodError.
Update: As pointed out in the comments, in actuallity, the method is defined on the Object class by default, which then gets auto inherited into all classes, but as private. This actually means that you will be getting the error saying that a private method is being called. The fix remains the same, since the overarching problem is a confusion in how to define classes and their methods.
The fix would be to define the method on the class, by putting it in the class body.
class Calc
attr_accessor :val1, :val2
def initialize (val1,val2)
#val1=val1
#val2=val2
end
def add_two_numbers(v3)
return #val1+#val2+v3
end
end
So you are defining a method outside of a class (which is want we don't want)
def add_two_numbers(v3)
return #val1+#val2+v3
end
You always want to make sure that you keep your classes and you logic as two separate entities in terms of organization. By that I mean:
Your classes in one file (calc.rb):
**class Calc
attr_accessor :val1, :val2
def initialize (val1,val2)
#val1=val1
#val2=val2
end
def add_two_numbers(v3)
return #val1+#val2+v3
end
end**
And your logic to access calc.rb in another file. Use require relative to access the logic inside the class file:
require_relative"/calc.rb"
a=Calc.new(2,3)
a.add_two_numbers(3)
Tip: When I was learning ruby the best way to keep them in two separate files for better organization.That way you know you don't have a method somewhere outside of the class. This would avoid your "no method error"
I am working on a project that requires very specific methods to be called on an ActiveRecord::Relation object. These methods cannot extend ActiveRecord::Relation because the Class has it's own initialize method to determine if the object should be collected. I have tried a dozen ways to handle this but because of method chaining in AR I have been unable to accomplish this. Currently I have monkey patched ActiveRecord::Relation with a method that converts it like so:
module ActiveRecord
class Relation
def to_claim_set
exec_queries unless loaded?
ClaimSet.new(#records)
end
end
end
Firstly I am sure this is an improper way to handle it. Secondly this causes me to have to call #to_claim_set constantly throughout the application.
I am hoping someone can assist on making this the default return after all method chaining is complete.
What I am hoping for is something like
Claim.policy_number('913006')
#=> ClaimSetObjectHere
But I need it to support chaining like AR does so that things like
Claim.policy_number('913006').by_program('Base')
#=> ClaimSetObjectHere
I also tried to patch the #where method inside Claim which works great unless I use a scope or I chain methods in which case it complains that ClaimSet does not define default_scoped?.
Any insight would be greatly appreciated. As for "Why would you want to do this" like I said I am constantly calling this method throughout the application and I need the methods defined in ClaimSet for this to function properly.
Note: This is being used outside of rails
Okay so what I ended up doing was imposing a wrapper for ActiveRecord::Relation like so:(removed specific business logic for brevity)
class ClaimSet
def initialize(object)
process_target(object)
# ...
end
# ...
def respond_to_missing?(method_name,include_private=false)
#target.respond_to?(method_name)
end
def method_missing(method_name, *args, &block)
if #target.respond_to?(method_name)
ClaimSet.new(#target.send(method_name,*args,&block))
else
super
end
end
private
def process_target(object)
#target = object if object.is_a?(ActiveRecord::Relation)
#target = object.target if object.is_a?(ClaimSet)
end
end
Then in the Claim class.
class Claim < ActiveRecord::Base
class << self
def where(*args)
ClaimSet.new(super(*args))
end
def localized_scope(name,proc)
scope_proc = lambda do |*args|
ClaimSet.new(proc.call(*args))
end
singleton_class.send(:define_method,name,scope_proc)
end
end
end
Then I define all my scopes as localized_scope e.g.
localized_scope :policy_number, ->(policy_number){where(policy_number: policy_number)}
Now it always returns a ClaimSet in place of an ActiveRecord::Relation for #where and #localized_scope and supports method chaining through #method_missing. It also removes the monkey patch on ActiveRecord::Relation.
If you have any other suggestions please let me know as I would be glad to entertain other ideas but this works for the time being.
Is it possible to do something like this in Ruby (1.9.2-p290)?
class SomeClass
include SomeModuleThatProvidesLotOfConstants
def build(&block)
singleton_class.instance_eval(&block)
end
end
obj = SomeClass.new
obj.build do
some_class_method SomeConstant, :an => :option
...
end
Where some_class_method is a method that is available to SomeClass (not to instances of it) and SomeConstant is a class/module that is in scope inside of SomeClass, but would have to be references as SomeClass::SomeConstant from outside.
I can get this working if I always pass fully-qualified class names inside my block, but I'm trying to effectively "re-scope" the block when it is invoked. Is this possible? I'm pretty sure RSpec and other such tools that make heavy use of blocks achieve something like this :)
Note that while I'm calling class methods from inside the block, I only want the changes to affect this individual singleton class, rather than propogate to all instances.
EDIT | Ok, here's the non-pseudo version of what I'm trying to achieve. I'm trying to add some DataMapper properties at runtime, but only to singleton classes... I don't want them to suddenly appear across all instances of the model.
class Post
include DataMapper::Resource
property :id, Serial
property :title, String
property :created_at, DateTime
... etc ...
def virtualize(&block)
singleton_class.instance_eval(&block)
self
end
end
def suspend_post
#post = Post.get!(1).virtualize do
property :delete_comments, Boolean
end
end
I know there are other ways to do virtual attributes (I'm currently using a couple of different approaches, depending on the complexity), but I'm just experimenting with a few ideas to avoid cluttering my model definitions with transient methods that are only used for transporting form data in one specific part of the site and don't mean anything when you're reading the source code of the model by itself. One or two virtual attributes are ok, but as they start to mount up on commonly used models I start to explore things like this ;)
In the above, the resource would have all of the standard properties defined in the concrete class, plus any that are added in the #virtualize method. It's the reference to Boolean without the DataMapper::Property:: prefix that's throwing it off.
You've already got what you want with respect to methods. If you define some_class_method like this:
def Foo.some_class_method(name)
define_method name do
puts("this is the method #{name}")
end
end
and do
f = Foo.new
f.build { some_class_method "new_method" }
f.singleton_methods # => [:new_method]
You've defined behavior on just that one instance.
However I don't think you can get what you're looking for with respect to constants. One option would be to use methods instead of constants for those arguments. Another would be to have the client code mix in whatever module defines the constants.
Do keep in mind this is pretty dense metaprogramming, so the complexity may not be justified.
What's wrong with this:
class SomeClass
SOME_CONSTANT = 42
class << self
def some_class_method
'foo'
end
end
def build &block
self.class.instance_eval(&block)
end
end
SomeClass.new.build do
puts "#{some_class_method} #{SOME_CONSTANT}"
end
#=>foo 42
Here is my situation:
XMLRPC::Client has a proxy constructor, new3, that takes a hash of options. It takes out the individual values to then delegate the construction to the default initializer, initialize
I am deriving from XMLRPC::Client. I want a class that is XMLRPC::Client but with some added functionality.
I want to be able to instantiate this derived class using a hash of options as well. This means that in my derived class' initializer, I have to somehow instantiate super using the new3 proxy constructor.
My Question Is if this is possible. If not, then is the only way to solve this is to practically 'copy and paste' the code within the XMLRPC::Client.new3 method into my derived class' constructor?
The reason I'm asking this is simply to see if there is a way of solving this problem, since there is this recurring theme of DRY (Don't Repeat Yourself) within the Ruby community. But of course, if this is the only way, it wont kill me.
I am only posting an answer supplement the other answers by showing you how XMLRPC's code is written
def new3(hash={})
# convert all keys into lowercase strings
h = {}
hash.each { |k,v| h[k.to_s.downcase] = v }
self.new(h['host'], h['path'], h['port'], h['proxy_host'], h['proxy_port'], h['user'], h['password'],
h['use_ssl'], h['timeout'])
end
http://www.ensta.fr/~diam/ruby/online/ruby-doc-stdlib/libdoc/xmlrpc/rdoc/classes/XMLRPC/Client.html
Make a new class method in your derived class (much like they did to make new3 in the first place):
class MyDerived < XMLRPC::Client
def self.new3(hashoptions)
client = XMLRPC::Client.new3(hashoptions)
# Do further initialisation here.
end
end
myone = MyDerived.new3(:abc => 123, ...)
super only works in initialize (and only changes the parameters to the superclass's initialize), so it doesn't apply here.
You should probably be able to call new3 on you subclass
class MyClient < XMLRPC::Client
end
MyClient.new3({})
Or overwrite it if you need to do extra stuff:
class MyClient < XMLRPC::Client
def self.new3(args)
client = super(args)
# do some more stuff
client
end
end
MyClient.new3({})
I'm writing a framework for querying the Mediawiki API. I have a Page class which represents articles on the wiki, and I've also got a Category class, which is-a Page with more specific methods (like being able to count the number of members in the category. I've also got a method Page#category? which determines if an instantiated Page object is actually representative of a Mediawiki category page, by querying the API to determine the namespace of the article.
class Page
def initialize(title)
# do initialization stuff
end
def category?
# query the API to get the namespace of the page and then...
namespace == CATEGORY_NAMESPACE
end
end
class Category < Page
# ...
end
What I would like to do is be able to detect if the user of my framework tries to instantiate a Mediawiki category using a Page object (ie. Page.new("Category:My Category")), and if so, instantiate a Category object, instead of a Page object, directly from the Page constructor.
It seems to me that this should be possible because it's reminiscent of single table inheritance in Rails, but I'm not sure how to go about getting it to work.
Ok, couple of things:
You can't convert an instance of a class A to an instance of A's subclass B. At least, not automatically. B can (and usually does) contain attributes not present in A, it can have completely different constructor etc. So, AFAIK, no OO language will allow you to "convert" classes that way.
Even in static-typed languages, when you instantiate B, and then assign it to a variable a of type A, it is still instance of B, it is not converted to its ancestor class whatsoever.
Ruby is a dynamic language with powerful reflection capabilities, so you can always decide which class to instantiate in the runtime - check this out:
puts "Which class to instantiate: "
class_name = gets.chomp
klass = Module.const_get class_name
instance = klass.new
So, no need for any conversion here - just instantiate the class you need in the first place.
Another thing: as I mentioned in the comment, method category? is simply wrong, as it violates OOP principles. In Ruby, you can - and should - use method is_a?, so your check will look like:
if instance.is_a? Category
puts 'Yes, yes, it is a category!'
else
puts "Nope, it's something else."
end
This is just a tip of the iceberg, there's lot more about instantiating different classes, and another question I have linked in the comment can be a great starting point, although some code examples there might confuse you. But it is definitely worth understanding them.
Edit: After re-reading your updated question, it seems to me that the right way for you would be to create a factory class and let it do the detecting and instantiating different page types. So, user wouldn't call Page.new directly, but rather call something like
MediaWikiClient.get_page "Category:My Category"
and get_page method would instantiate corresponding class.
Why not something like this? Being able to do that is a good enough reason to do it!
class Page
def self.new(title)
if self == Page and is_category?(title)
Category.new(title)
else
super
end
end
def self.is_category?(title)
# ... (query the API etc.)
end
def initialize(title)
# do initialization stuff
end
def category?
# query the API to get the namespace of the page and then...
namespace == CATEGORY_NAMESPACE
end
end
class Category < Page
# ...
end
You could define a method that instantiate the class and returns the instance.
This is know as Factory Pattern
class PageFactory
def create(title) # the pattern uses "create".. but "new" is more Ruby' style
namespace = title[/\A[^:]+(?=:)/]
# matches the prefix, up to and excluding the first colon.
if namespace == CATEGORY_NAMESPACE
Category.new(title)
else
Page.new(title)
end
end
end
class ClientClass
def do_something()
factory = PageFactory.new
my_page = factory.create('Category:Foo')
my_page.do_something()
end
end