good morning people
I'm trying to validate within the module if the email is valid but I'm getting an error can you help me?
require 'active_model'
class PixKey
attr_accessor :key
include ActiveModel::Validations
def initialize(key)
validate :key, format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
#key == :key
end
end
person = PixKey.new('me#vishnuatrai.com') puts person.valid?
Err
Uncaught exception: wrong number of arguments (given 2, expected 0..1)
/home/wesleybedeschi/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/activemodel-7.0.3/lib/active_model/validations.rb:334:in
`valid?'
You need to use validates method here not validate. Also you need to refactor your code a little bit like his:
Class Pixkey
include ActiveModel::Validations
attr_accessor :key
validates :key, format: {with:/\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
def initialize(key)
#key = key
end
end
Related
I'm trying to figure out a way to dynamically generate subclasses based on a parent class. In my specific case I'd want to have attr_accessor for every instance variable, initialized in my Parent class and inherited on the SubClasses.
My classes are three different models representing three different tables in a DB.
"Record" is my parent class where I want to store and write all of my code.
"Post" and "User" are the Subclasses inheriting.
My code
class Record
attr_reader :id
# attr_accessor
def initialize(**params)
#id = params[:id]
instance_variable_set("##{params.keys[0]}", params.values[0])
instance_variable_set("##{params.keys[1]}", params.values[1])
instance_variable_set(:#votes, params["votes"] || 0) if instance_of?(Post)
# p self.title
end
Want I want to achieve is setting attr_accessor as for example in my Subclass "Post" I want to call
post = Post.new(title: "New post", url: "some url")
puts post.title
I can access the title instance variable without raising a NoMethodError
Could someone guide me, or give me some hint?
Thanks
You're going about it backwards. A parent class should not have to know about or implement specific logic for its subclasses.
class Record
attr_reader :id
def initialize(**attributes)
attributes.each do |key, value|
send("#{key}=", value)
end
end
end
class Post < Record
attr_accessor :title
attr_accessor :votes
end
irb(main):066:0> Post.new(id: 1, votes: 10, title: "Hello World").title
=> "Hello World"
attr_accessor is just a metaprogramming convenience for defining methods so your accessor methods are inherited anyways. But if you're writing something like an Object Relational Manager you'll want to define your own macro method for defining attributes that lets you keep track of the attributes of a class:
module Attributes
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
#attributes ||= {}
end
end
# assigns the passed attributes to the instance
def initialize(**attributes)
attributes.each do |key, value|
send "#{key}=", value
end
end
# gets all the attributes of an instance
def attributes
self.class.attributes.keys.each_with_object(Hash.new) do |key, hash|
hash[key] = send(key)
end
end
module ClassMethods
# Inherits the attributes of the parent class
def inherited(subclass)
attributes.tap do |parent_attributes|
subclass.class_eval do
#attributes ||= {}.merge(parent_attributes)
end
end
end
# defines an attribute that is inherited
def attribute(name, type = nil, **kwargs)
#attributes[name] = { type: type }.merge(kwargs)
attr_accessor name
end
def attributes
#attributes
end
end
end
class Record
include Attributes
attribute :id, Integer
end
class Post < Record
attribute :title, String
attribute :votes, Integer
end
irb(main):101:0> Post.new(votes: 10, title: "Hello World").attributes
=> {:id=>nil, :title=>"Hello World", :votes=>10}
This stores the attribute definitions in a class instance variable which lets you attach "metadata" which opens up for features that you will want later such as typecasting, serialization and dirty tracking.
I find myself using hash arguments to constructors quite a bit, especially when writing DSLs for configuration or other bits of API that the end user will be exposed to. What I end up doing is something like the following:
class Example
PROPERTIES = [:name, :age]
PROPERTIES.each { |p| attr_reader p }
def initialize(args)
PROPERTIES.each do |p|
self.instance_variable_set "##{p}", args[p] if not args[p].nil?
end
end
end
Is there no more idiomatic way to achieve this? The throw-away constant and the symbol to string conversion seem particularly egregious.
You don't need the constant, but I don't think you can eliminate symbol-to-string:
class Example
attr_reader :name, :age
def initialize args
args.each do |k,v|
instance_variable_set("##{k}", v) unless v.nil?
end
end
end
#=> nil
e1 = Example.new :name => 'foo', :age => 33
#=> #<Example:0x3f9a1c #name="foo", #age=33>
e2 = Example.new :name => 'bar'
#=> #<Example:0x3eb15c #name="bar">
e1.name
#=> "foo"
e1.age
#=> 33
e2.name
#=> "bar"
e2.age
#=> nil
BTW, you might take a look (if you haven't already) at the Struct class generator class, it's somewhat similar to what you are doing, but no hash-type initialization (but I guess it wouldn't be hard to make adequate generator class).
HasProperties
Trying to implement hurikhan's idea, this is what I came to:
module HasProperties
attr_accessor :props
def has_properties *args
#props = args
instance_eval { attr_reader *args }
end
def self.included base
base.extend self
end
def initialize(args)
args.each {|k,v|
instance_variable_set "##{k}", v if self.class.props.member?(k)
} if args.is_a? Hash
end
end
class Example
include HasProperties
has_properties :foo, :bar
# you'll have to call super if you want custom constructor
def initialize args
super
puts 'init example'
end
end
e = Example.new :foo => 'asd', :bar => 23
p e.foo
#=> "asd"
p e.bar
#=> 23
As I'm not that proficient with metaprogramming, I made the answer community wiki so anyone's free to change the implementation.
Struct.hash_initialized
Expanding on Marc-Andre's answer, here is a generic, Struct based method to create hash-initialized classes:
class Struct
def self.hash_initialized *params
klass = Class.new(self.new(*params))
klass.class_eval do
define_method(:initialize) do |h|
super(*h.values_at(*params))
end
end
klass
end
end
# create class and give it a list of properties
MyClass = Struct.hash_initialized :name, :age
# initialize an instance with a hash
m = MyClass.new :name => 'asd', :age => 32
p m
#=>#<struct MyClass name="asd", age=32>
The Struct clas can help you build such a class. The initializer takes the arguments one by one instead of as a hash, but it's easy to convert that:
class Example < Struct.new(:name, :age)
def initialize(h)
super(*h.values_at(:name, :age))
end
end
If you want to remain more generic, you can call values_at(*self.class.members) instead.
There are some useful things in Ruby for doing this kind of thing.
The OpenStruct class will make the values of a has passed to its initialize
method available as attributes on the class.
require 'ostruct'
class InheritanceExample < OpenStruct
end
example1 = InheritanceExample.new(:some => 'thing', :foo => 'bar')
puts example1.some # => thing
puts example1.foo # => bar
The docs are here:
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/ostruct/rdoc/OpenStruct.html
What if you don't want to inherit from OpenStruct (or can't, because you're
already inheriting from something else)? You could delegate all method
calls to an OpenStruct instance with Forwardable.
require 'forwardable'
require 'ostruct'
class DelegationExample
extend Forwardable
def initialize(options = {})
#options = OpenStruct.new(options)
self.class.instance_eval do
def_delegators :#options, *options.keys
end
end
end
example2 = DelegationExample.new(:some => 'thing', :foo => 'bar')
puts example2.some # => thing
puts example2.foo # => bar
Docs for Forwardable are here:
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/forwardable/rdoc/Forwardable.html
Given your hashes would include ActiveSupport::CoreExtensions::Hash::Slice, there is a very nice solution:
class Example
PROPERTIES = [:name, :age]
attr_reader *PROPERTIES #<-- use the star expansion operator here
def initialize(args)
args.slice(PROPERTIES).each {|k,v| #<-- slice comes from ActiveSupport
instance_variable_set "##{k}", v
} if args.is_a? Hash
end
end
I would abstract this to a generic module which you could include and which defines a "has_properties" method to set the properties and do the proper initialization (this is untested, take it as pseudo code):
module HasProperties
def self.has_properties *args
class_eval { attr_reader *args }
end
def self.included base
base.extend InstanceMethods
end
module InstanceMethods
def initialize(args)
args.slice(PROPERTIES).each {|k,v|
instance_variable_set "##{k}", v
} if args.is_a? Hash
end
end
end
My solution is similar to Marc-André Lafortune. The difference is that each value is deleted from the input hash as it is used to assign a member variable. Then the Struct-derived class can perform further processing on whatever may be left in the Hash. For instance, the JobRequest below retains any "extra" arguments from the Hash in an options field.
module Message
def init_from_params(params)
members.each {|m| self[m] ||= params.delete(m)}
end
end
class JobRequest < Struct.new(:url, :file, :id, :command, :created_at, :options)
include Message
# Initialize from a Hash of symbols to values.
def initialize(params)
init_from_params(params)
self.created_at ||= Time.now
self.options = params
end
end
Please take a look at my gem, Valuable:
class PhoneNumber < Valuable
has_value :description
has_value :number
end
class Person < Valuable
has_value :name
has_value :favorite_color, :default => 'red'
has_value :age, :klass => :integer
has_collection :phone_numbers, :klass => PhoneNumber
end
jackson = Person.new(name: 'Michael Jackson', age: '50', phone_numbers: [{description: 'home', number: '800-867-5309'}, {description: 'cell', number: '123-456-7890'})
> jackson.name
=> "Michael Jackson"
> jackson.age
=> 50
> jackson.favorite_color
=> "red"
>> jackson.phone_numbers.first
=> #<PhoneNumber:0x1d5a0 #attributes={:description=>"home", :number=>"800-867-5309"}>
I use it for everything from search classes (EmployeeSearch, TimeEntrySearch) to reporting ( EmployeesWhoDidNotClockOutReport, ExecutiveSummaryReport) to presenters to API endpoints. If you add some ActiveModel bits you can easily hook these classes up to forms for gathering criteria. I hope you find it useful.
I have created an item class and am trying to rake test it. When running the code outside the test no errors are thrown. Because of this i assume i am testing it wrong.
class Item
attr_accessor :name, :description, :item
def initialize (item, description, name)
#name = item[:name]
#description = item[description]
end
end
And the code i am using to test it is
require "Asheron's_call/Item.rb"
require "Test/Unit"
class TestGame < Test::Unit::TestCase
def test_item
one = Item.new ("Potion","Red")
assert_equal("Potion", one.name)
end
end
=>93: one = Item.new ("Potion","Red")
94: assert_equal("Potion", one.name
when running the test with that i am getting a new error which is a syntax error. It is expecting a ')' after potion. When i changed that to see what would happen and then came back saying it expected me to place 'end' which to me feels wrong.
The test is fine. The Item’s constructor is not:
class Item
attr_accessor :name, :description
def initialize (name, description)
#name = name
#description = description
end
end
There is no item there. Also, in the test one should assert the instance, not the class:
# wrong: assert_equal("Potion", Item.name)
assert_equal("Potion", one.name)
I was reading the jbuilder's README and saw these code:
class Person
# ... Class Definition ... #
def to_builder
Jbuilder.new do |person|
person.(self, :name, :age)
end
end
end
I tried to replicate it myself, and it asks for a call method, so:
class Thing
attr_accessor :name, :age
def call(*args)
puts args.inspect
end
end
Thing.new.(:name, :age) # => [:name, :age]
So why is there a self in the jbuilder call?
self here is just a parameter passed to the Jbuilder's call method.
Jbuilder needs the instance of person (which is self in the code) and the attribute names (:name and :age in the code) to produce the json data.
Example:
class Thing
attr_accessor :name, :age
def call(*args)
puts args.inspect
end
end
class Bar
def to_thing
Thing.new.(self, :name, :age)
end
end
Bar.new.to_thing
module Framework
class CreateTableDefinition
attr_accessor :host, :username, :password
end
end
def create_table(table_name)
obj = Framework::CreateTableDefinition.new
yield(obj) if block_given?
end
create_table :users do |config|
config.host :localhost
end
And here is the error I get
-:13:in `block in <main>': wrong number of arguments (1 for 0) (ArgumentError)
from -:9:in `create_table'
from -:12:in `<main>'
If I change the code to
config.host = :localhost
it works fine. But what I want is to work as described above config.host :localhost
You missed assignment:
config.host = :localhost
Edit
If you want to get rid of assignments, you need to define setter methods without = at the end. This might generate quote a lot of code, so I would rather go with some meta-programming (because it's fun!)
class MyConfigClass
def self.attributes(*args)
args.each do |attr|
define_method attr do |value|
#attributes[attr] = value
end
end
end
def initialize
#attributes = {}
end
def get(attr)
#attributes[attr]
end
end
class CreateTableDefinition < MyConfigClass
attributes :host, :username, :password
end
c = CreateTableDefinition.new
c.host :localhost
c.get(:host) #=> :localhost
Try manually making a method that does what you want instead of using the attr_accessor shortcut.
class CreateTableDefinition
attr_accessor :username, :password
def host(sym)
#host = sym
end
def get_host
#host
end
end
If you don't like the idea of writing those methods for every attribute, look into writing your own helper, something like attr_rails_like, and mix it in to the Class object. This article might be helpful.