I have a parent class that looks like this:
class Record
attr_accessor :id, :url, :votes, :title, :first_name, :last_name, :selfdb
def initialize(args = {})
args.each { |name, value| instance_variable_set("##{name}", value) }
#selfdb = "#{self.class.name.downcase}s"
end
def self.find(id)
DB.results_as_hash = true
hasharray = DB.execute("SELECT * FROM ? WHERE id = ?", #selfdb, id)
hasharray.empty? ? nil : new(hasharray[0].transform_keys(&:to_sym))
end
end
Each child class of Record has a matching database table whose name is "#{name of the class}s", so the class "Post" is connected to a table named "posts".
My goal is to have self.find(id) to work on any children of this class. The solution I tried was to save the class' name into a string variable with an "s" at the end (so that class Post -> "posts", for example), to match the name of the database, as I tried in the instance variable #selfdb, but this does not work.
Calling #selfdb on the children classes confirms that it does correctly create the string for different classes, but running the sqlite with it inserted as the table name just returns nil.
This might be a very roundabout way of doing it, any suggestions are welcome. I am still learning and this is just a bootcamp assignment.
Edit: i realized one mistake I made: since self.find(id) is a class method, it can't use an instance variable. However, when I change the class method to work like this:
def self.find(id)
selfdb = "#{self.name.downcase}s"
DB.results_as_hash = true
hasharray = DB.execute("SELECT * FROM ? WHERE id = ?", selfdb, id)
hasharray.empty? ? nil : new(hasharray[0].transform_keys(&:to_sym))
end
... it still does not properly insert into the sqlite string.
You define #selfdb in the initialize method which means it is only available in on the instance level. But your self.find method is a class method and therefore #selfdb is undefined on the class level.
I would suggest adding a class method that returns the table name like this
def self.table_name
"#{name.downcase}s"
end
which you can then be used in the find class method like this
def self.find(id)
# ...
hasharray = DB.execute("SELECT * FROM ? WHERE id = ?", table_name, id)
# ...
end
and in instance methods (for example to save a record) you need to use self.class.table_name or you could add a delegator to forward a table_name instance method call to the class method:
extend Forwardable
def_delegators :"self.class", :table_name
Related
I need to create a class where if the attribute value is the same it does not generate a new object id, example:
result:
described_class.new('01201201202')
<PixKey:0x00007eff5eab1ff8 #key="01201201202">
if i run it again with the same value it should keep the same object id
0x00007eff5eab1ff8
is similar behavior with the symbol
test:
describe '#==' do
let(:cpf) { described_class.new('01201201202') }
it 'verifies the key equality' do
expect(cpf).to eq described_class.new('01201201202')
end
end
Running the test shows an error, because the obejct id changes:
expected: #<PixKey:0x00007eff5eab1ff8 #key="01201201202">
got: #<PixKey:0x00007eff5eab2070 #key="01201201202">
Class:
class PixKey
def init(key)
#key = key
end
end
The other answers are fine, but they are a little more verbose than needed and they use class variables, which I find to be a confusing concept because of how they are shared among various classes.
class PixKey
#instances = {}
def self.new(id)
#instances[id] ||= super(id)
end
def initialize(id)
#key = id
end
end
p PixKey.new(1)
p PixKey.new(2)
p PixKey.new(2)
p PixKey.new(1)
Running the test shows an error, because the object id changes
Not quite. It shows an error because the objects are not equal. And the error message prints both objects including their id. But the object id is not what's causing the test to fail.
I need to create a class where if the attribute value is the same it does not generate a new object id
That would probably work, but you're likely approaching the problem from the wrong side. In Ruby, equality doesn't mean object identity. Two objects can be equal without being the same object, e.g.
a = 'foo'
b = 'foo'
a.object_id == b.object_id
#=> false
a == b
#=> true
There's no need to tinker with object ids to get your test passing. You just have to implement a custom == method, e.g.:
class PixKey
attr_reader :key
def initialize(key) # <- not "init"
#key = key
end
def ==(other)
self.class == other.class && self.key == other.key
end
end
The == method checks if both objects have the same class (i.e. if both are PixKey instances) and if their key's are equal.
This gives:
a = PixKey.new('01201201202')
b = PixKey.new('01201201202')
a == b
#=> true
Create a class method to create instances and have it look up a hash.
class PixKey
##instances = {}
def PixKey.create(id)
if not ##instances.has_key?(id)
##instances[id] = PixKey.new(id)
end
return ##instances[id]
end
def initialize(id)
#key = id
end
end
a = PixKey.new(123)
b = PixKey.new(123)
c = PixKey.create(123)
d = PixKey.create(123)
puts a
puts b
puts c
puts d
Output:
#<PixKey:0x000000010bc39900>
#<PixKey:0x000000010bc38078>
#<PixKey:0x000000010bc33eb0>
#<PixKey:0x000000010bc33eb0>
Notice the last two instances created with the PixKey.create(id) method return the same instance.
Note that Ruby's new method is just a method on Class and can be overridden like any other. The docs describe the default implementation.
Calls allocate to create a new object of class's class, then invokes that object's initialize method, passing it args. This is the method that ends up getting called whenever an object is constructed using .new.
So, if you want to keep the .new syntax and still get the same objects back, we can override new on the class and call super. This is exactly what OscarRyz' answer does, just with .new and super rather than a separate helper function.
class PixKey
##instances = {}
def PixKey.new(id)
if not ##instances.has_key?(id)
##instances[id] = super(id)
end
return ##instances[id]
end
def initialize(id)
#key = id
end
end
a = PixKey.new(123)
b = PixKey.new(123)
puts a
puts b
I was trying this practice problem on assigning attributes to columns in a table (more specifically the self.finalize! method):
class SQLObject
def self.columns
return #columns if #columns
results = DBConnection.execute2(<<-SQL).first
SELECT
*
FROM
"#{table_name}"
LIMIT 0
SQL
results.map!(&:to_sym)
#columns = results
end
def self.finalize!
# can use "columns" instead of "self.columns" b/c "columns" is a class method
# defined on the class itself
info = self.attributes
self.columns.each do |column|
define_method(column) { info[column] }
define_method("#{column}=") do |val|
info[column] = val
end
end
def attributes
#attributes ||= {}
end
end
And I found that using "info" to store the results from the attributes instance method (which from what I understand, is only declared on this SQLObject class) gave me a NoMethodError that looked like this:
NoMethodError:
undefined method `attributes' for Cat:Class
Why does using "info" to store the call to the "attributes" method not work? And why is explicitly saying "self.attributes[column]..." in the "self.finalize!" method correct?
I want to use the ActiveRecord to manupulate my tables data, but what if my table name is variable. This code, will generate an error. Any suggestion?
name = "Posts"
class name < ActiveRecord::Base
end
I believe you can do something like the following:
name = 'Post' # Dynamically generate the class name
klass = Class.new(ActiveRecord::Base) do
# This is the base class.
# You can pass a block with new
# methods here if you'd like.
end
Object.const_set name, klass
name.constantize.new # Call your class dynamically :)
You can do something like this to set the table_name from a variable:
class Post < ActiveRecord::Base
def self.table_name
# Some method to determine and set the table name.
# Must return a string with a table name. E.g. the
# following would set the table name to `posts_2015-11-15`
"posts_#{Date.today}"
end
# ...
end
Is it possible in Ruby to instantiate another object of a different class using a class method?
I have spent a lot of time to no avail researching Google, the Ruby docs, and Stack Overflow, for an answer to my query, so I am posting this question as a last resort.
Two classes, User, and Blog. In User below I am trying to create a new instance of an object for Blog, and carry over some attributes.
In class User there are 2 methods;
class User
attr_accessor :blogs, :username
def initialize(username)
self.username = username
self.blogs = []
end
def add_blog(date, text)
self.blogs << [Date.parse(date),text]
new_blog = [Date.parse(date),text]
Blog.new(#username,date,text)
end
end
Using the above add_blog I would like to initialize & send a new object to the Blog class.
class Blog
attr_accessor :text, :date, :user
def initialize(user,date,text)
user = user
date = date
text = text
end
end
Yes, it is possible to instantiate another object of a different class using a class method. We ruby programmers are doing it all the time.
Do you want to have the blogs attribute of the user to have an array of blogs? Because your code just puts a date-text tupel into the array.
I think you wanted to do something like this in your user class:
def add_blog(date, text)
parsed_date = Date.parse(date)
new_blog = Blog.new(#username, parsed_date, text)
self.blogs << new_blog
new_blog
end
I have showed it step after step, but you can combine several lines. The last line returns the new blog, you may not need it if you only want the blog be part of the blogs array.
Looks like your code has some flaws. Here is the corrected code:
You were not using # to assign values to instance variables, you were putting a date in #blogs, instead of Blog object.
If you want to pass instance of User to Blog from add_blog, you can use self which represents current instance of User class. If you want to carry just some attributes, then, you can do so by referring to attributes using #attribute_name or self.attribute_name syntax
require "date"
class User
attr_accessor :blogs, :username
def initialize(username)
#username = username
#blogs = []
end
def add_blog(date, text)
#blogs << Blog.new(#username, Date.parse(date),text)
self
end
def to_s
"#{#username} - #{#blogs.collect { |b| b.to_s }}"
end
end
class Blog
attr_accessor :text, :date, :user
def initialize(user,date,text)
#user = user
#date = date
#text = text
end
def to_s
"Blog of #{user}: #{#date} - #{#text}"
end
end
puts User.new("Wand Maker").add_blog("2015-08-15", "Hello")
# => Wand Maker - ["Blog of Wand Maker: 2015-08-15 - Hello"]
I see code like:
class Person
def initialize(name)
#name = name
end
end
I understand this allows me to do things like person = Person.new and to use #name elsewhere in my class like other methods. Then, I saw code like:
class Person
attr_accessor :name
end
...
person = Person.new
person.name = "David"
I'm just at a loss with these two methods mesh. What are the particular uses of def initialize(name)? I suppose attr_accessor allows me to read and write. That implies they are two separate methods. Yes? Want clarifications on def initialize and attr_accessor and how they mesh.
initialize and attr_accessor have nothing to do with each other. attr_accessor :name creates a couple of methods:
def name
#name
end
def name=(val)
#name = val
end
If you want to set name upon object creation, you can do it in the initializer:
def initialize(name)
#name = name
# or
# self.name = name
end
But you don't have to do that. You can set name later, after creation.
p = Person.new
p.name = "David"
puts p.name # >> "David"
Here is the answer you are looking for Classes and methods. Read it carefully.
Here is a good documentation from the link:
Classes and methods
Now we are ready to create our very own Address class. Let's start simple. Let's start with an address that only contains the "street" field.
This is how you define a class:
class Address
def initialize(street)
#street = street
end
end
Let's go through this:
The class keyword defines a class.
By defining a method inside this class, we are associating it with this class.
The initialize method is what actually constructs the data structure. Every class must contain an initialize method.
#street is an object variable. Similar to the keys of a hash. The # sign distinguishes #street as an object variable. Every time you create an object of the class Address, this object will contain a #street variable.
Let's use this class to create an address object.
address = Addres.new("23 St George St.")
That's it. address is now an object of the class Address
Reading the data in an object
Suppose that we want to read the data in the address object. To do this, we need to write a method that returns this data:
class Address
def initialize(street)
#street = street
end
# Just return #street
def street
#street
end
end
Now the method Address#street lets you read the street of the address. In irb:
>> address.street
=> "23 St George St."
A property of an object, which is visible outside, is called an attribute. In this case, street is is an attribute. In particular, it is a readable attribute. Because this kind of attribute is very common, Ruby offers you a shortcut through the attr_reader keyword:
class Address
attr_reader :street
def initialize(street)
#street = street
end
end
Changing the data in an object
We can also define a method to change the data in an object.
class Address
attr_reader :street
def initialize(street)
#street = street
end
def street=(street)
#street = street
end
end
Ruby is pretty smart in its use of the street= method:
address.street = "45 Main St."
Notice that you can put spaces betten street and =. Now that we can change the address data, we can simplify the initialize method, and have it simply default the street to the empty string "".
class Address
attr_reader :street
def initialize
#street = ""
end
def street=(street)
#street = street
end
end
address = Address.new
address.street = "23 St George St."
This might not seem like much of a simplification, but when we add the city, state and zip fields, and more methods this will make the class definition a bit simpler.
Now, street is also a writable attribute. As before, you can declare it as such with attr_writer:
class Address
attr_reader :street
attr_writer :street
def initialize
#street = ""
end
end
Accessing data
Very often, you have attributes that are both readable and writable attributes. Ruby lets you lump these together with attr_accessor. I guess these would be called "accessible attributes", but I have never seen them be called that.
class Address
attr_accessor :street
def initialize
#street = ""
end
end
With this knowledge, it is now easy to define the entire addressbook structure. As it turns out, attr_accessor and friends all accept multiple arguments.
class Address
attr_accessor :street, :city, :state, :zip
def initialize
#street = #city = #state = #zip = ""
end
end
I think you consider initialize as a constructor. To be precise, it is not. The default constructor is the new method on the class, and initialize is called by that method. If you do not define initialize, you can still create an object with new because initialize is not the constructor itself. In that case, the default initialize does nothing. If you do define initialize, then that is called right after the object creation.
The statement #foo = ... and attr_accessor :foo are different. The former assigns a value to the instance variable #foo, whereas the latter lets you access #foo via methods foo and foo=. Without the latter, you can still access #foo by directly describing so.
Unlike C++,Java instance variables in Ruby are private by default(partially as they can be accessed by using a.instance_variable_get :#x)
eg:
class Dda
def initialize task
#task = task
#done = false
end
end
item = Dda.new "Jogging" # This would call the initializer and task = Jogging would
be set for item
item.task # would give error as their is no function named task to access the instance
variable.
Although we have set the value to item but we won't be able to do anything with it as instace variables are private in ruby.
code for getter:
def task
#task
end
#for getter
def task=task
#task = task
end
Using getter would ensure that item.task returns it's value
And using setter gives us the flexibility to provide values to instance variables at any time.