I've been tasked with writing a function to validate that the first name and surname are not the same in the code below. I have to use ActiveModel::Validation and ActiveModel::Errors and if the two names are the same it should give an error message "Nope".
I have very little ruby experience, but here's my attempt:
require 'active_model'
class Person
include ActiveModel::Validations
validate :test
validates_presence_of :first
validates_presence_of :last
def initialize(first, last)
#first = first
#last = last
end
def test
errors.add_to_base("Nope") if #first == #last
end
end
a = Person.new("James", "James")
b = Person.new("James")
So I get an error message when I try to instantiate b but that's just a Ruby error because my function is missing arguments. I'm sure this is probably pretty simple, but I'd be really grateful for any help.
Check https://github.com/rails/rails/tree/master/activemodel for more info:
require 'active_model'
class TestValidator < ActiveModel::Validator
def validate(record)
record.errors[:base] = "First and last can't be the same" if record.first.presence == record.last.presence
end
end
class Person
include ActiveModel::Model
attr_accessor :first, :last
validates_presence_of :first, :last
validates_with TestValidator
end
p = Person.new(first: "Nick", last: "Nick")
puts p.valid? # => false
puts p.errors.full_messages # => First and last can't be the same
p = Person.new(first: "Nick", last: "Doe")
puts p.valid? # => true
I came up with my own solution to this, what I was looking for was:
require 'active_model'
class Person
include ActiveModel::Validations
attr_accessor :first, :last
validate :first_does_not_equal_last
def first_does_not_equal_last
if #first == #last
self.errors[:base] << "First cannot be the same as Last"
end
end
def initialize(first, last)
#first = first
#last = last
end
end
Related
I am trying to get programm started where i cant read in a csv File an it prints the data out on a pdf-File. Now i have a problem.
Heres is my Code:
------------------------------------
require_relative 'friends'
class List
attr_accessor :list_name, :list
def initialize(list_name)
#list_name = list_name
#list = []
end
def list_name
#list_name
end
def liste
#list
end
def wert(place)
#list[place].to_s
end
def list_length
#list.length
end
def print_list
#list.each do |freunde|
"#{freunde.name},#{freunde.age}"
end
end
def add_friend(a_friend)
#list.push(a_friend)
end
def load_friend(from_file)
File.readlines(from_file).each do |line|
add_friend(Freunde.from_csv(line))
end
end
end
-------------------------------------------
require_relative 'list'
class Friends
attr_accessor :name,:age
def initialize(name, age)
#name = name
#age = age
end
def self.from_csv(string)
name, age = string.split(',')
Freunde.new(name,age)
end
def friends
#name
end
end
-------------------------------------------
require 'prawn'
require_relative 'list'
require_relative 'friends'
class Generating
include Prawn::View
def initialize
#document = Prawn::Document.new(:page_size => "A4")
#fontpath = File.expand_path("../data/fonts", __FILE__)
liste1 = Listen.new("Friendslist")
liste1.load_friend("test.csv")
print_list
save
end
def print_friends
font("#{#fontpath}/Arial.ttf") do
font_size 11
text_box("#{liste1.print_list}", :at => [15,405], :height => 50,
:width => 250)
end
end
def save
self.render_file "Hello.pdf"
end
end
---------------------------------------------
When i now create a new generating-Object:
gen = Generating.new
then it fails the whole programm because the error says method unknow (print_list). Am i submitting the wrong object for the method(print_list), or am using the text output methods of prawn wrong?
print_list is an instance method of List class, and you call it on self object, which is there an instance of Generating. It should be:
liste1 = Listen.new("Friendslist")
liste1.load_friend("test.csv")
#⇓⇓⇓⇓⇓⇓
liste1.print_list
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
I'm a beginner at programming and wrote a simple program:
class Chapter
def initialize
#text
#number
end
end
def new_chapter
tmp_chapter = Chapter.new
tmp_chapter.text = 'Chapter about ..'
tmp_chapter.number = '11'
end
puts new_chapter
puts ObjectSpace.each_object(Chapter) {|x| p x}
But I get this error:
test2.rb:10:in `new_chapter': undefined method `text=' for #<Chapter:0x200b830>
(NoMethodError)
from test2.rb:14:in `<main>'
So what did I do wrong? I know there are other ways to create a new instance but I want to do it this way! Thanks!
You have to this :
class Chapter
attr_accessor :text, :number
def initialize
#text
#number
end
end
You could write this as below,no need of def initialize ;#text; #number; end.
class Chapter
attr_accessor :text,:number
end
def new_chapter
tmp_chapter = Chapter.new
tmp_chapter.text = 'Chapter about ..'
tmp_chapter.number = '11'
end
puts new_chapter
puts ObjectSpace.each_object(Chapter) {|x| p x}
# >> 11
# >> #<Chapter:0x9596eac #text="Chapter about ..", #number="11">
# >> 1
You haven't made any accessors for your variables. Add these
attr_accessor :text
attr_accessor :number
See this question
Very simple example:
Model:
require 'inventory'
class CustomerOrder < ActiveRecord::Base
validates_presence_of :name
validate :must_have_at_least_one_item, :items_must_exist
before_save :convert_to_internal_items
attr_accessor :items
after_initialize do
#convert the internal_items string into an array
if internal_items
self.items ||= self.internal_items.split(',').collect { |x| x.to_i }
else
# only clobber it if it hasn't been set yet, like in the constructor
self.items ||= []
end
end
private
def convert_to_internal_items
#TODO: convert the items array into a string
self.internal_items = self.items.join(',')
end
def must_have_at_least_one_item
self.items.size >= 1
end
def items_must_exist
self.items.all? do |item|
Inventory.item_exists?(item)
end
end
end
Inventory is a singleton that should provide access to another service out there.
class Inventory
def self.item_exists?(item_id)
# TODO: pretend real code exists here
# MORE CLARITY: this code should be replaced by the mock, because the actual
# inventory service cannot be reached during testing.
end
end
Right now the service does not exist, and so I need to mock out this method for my tests. I'm having trouble doing this the Right Way(tm). I'd like to have it be configurable somehow, so that I can put in the mock during my tests, but have the normal code run in the real world.
There's probably something I'm not wrapping my head around correctly.
EDIT: to be more clear: I need to mock the Inventory class within the validation method of the model. Eventually that will talk to a service that doesn't exist right now. So for my tests, I need to mock it up as if the service I were talking to really existed. Sorry for the confusion :(
Here's what I'd like to have in the specs:
describe CustomerOrder do
it "should not accept valid inventory items" do
#magical mocking that makes Inventory.item_exists? return what I want
magic.should_receive(:item_exists?).with(1).and_return(false)
magic.should_receive(:item_exists?).with(2).and_return(true)
co = CustomerOrder.new(:name => "foobar", :items => [1,2]
co.should_not be_valid
end
it "should be valid with valid inventory items" do
#magical mocking that makes Inventory.item_exists? return what I want
magic.should_receive(:item_exists?).with(3).and_return(true)
magic.should_receive(:item_exists?).with(4).and_return(true)
co = CustomerOrder.new(:name => "foobar", :items => [3,4]
co.should be_valid
end
end
Using rails 3.0.3, rspec 2 and cucumber. Of course, only the rspec part matters.
require 'spec_helper'
describe CustomerOrder do
it "is invalid without an existing Inventory item" do
item = mock('item')
customer = Customer.new(:name=>"Moe")
customer.stub(:items) { [item] }
Inventory.should_receive(:item_exists?).with(item).and_return(true)
customer.should_not be_valid
end
end
Note: untested.
The way I ended up solving this follows
Inventory class:
require 'singleton'
class Inventory
include Singleton
def self.set_mock(mock)
#mock = mock
end
def self.item_exists?(item_id)
return #mock.item_exists?(item_id) if #mock
# TODO: how should I stub this out for the api
end
end
CustomerOrder model:
require 'inventory'
class CustomerOrder < ActiveRecord::Base
validates_presence_of :name
validate :must_have_at_least_one_item, :items_must_exist
before_save :convert_to_internal_items
attr_accessor :items
after_initialize do
#convert the internal_items string into an array
if internal_items
self.items ||= self.internal_items.split(',').collect { |x| x.to_i }
else
# only clobber it if it hasn't been set yet, like in the constructor
self.items ||= []
end
end
private
def convert_to_internal_items
#TODO: convert the items array into a string
self.internal_items = self.items.join(',')
end
def must_have_at_least_one_item
errors.add(:items, "Must have at least one item") unless self.items.size >= 1
end
def items_must_exist
failed = self.items.find_all do |item|
!Inventory.item_exists?(item)
end
if !failed.empty? then
errors.add(:items, "Items with IDs: [#{failed.join(' ')}] are not valid")
end
end
end
CustomerOrder specs:
require 'spec_helper'
describe CustomerOrder do
fixtures :all
before do
fake = double('fake_inventory')
fake.stub(:item_exists?) do |val|
case val
when 1
true
when 2
true
when 3
false
end
end
Inventory.set_mock(fake)
#GRR, skipping my fixtures right now
#valid_order = CustomerOrder.new(:name => "valid order",
:items => [1,2])
end
it "should require a name and at least one item" do
co = CustomerOrder.new(:name => "valid", :items => [1])
co.should be_valid
end
it "should not be valid without any items" do
#valid_order.items = []
#valid_order.should_not be_valid
end
it "should not be valid without a name" do
#valid_order.name = nil
#valid_order.should_not be_valid
end
it "should expose items instead of internal_items" do
#valid_order.should respond_to(:items)
end
it "should be able to treat items like an array" do
#valid_order.items.size.should == 2
#valid_order.items.should respond_to(:<<)
#valid_order.items.should respond_to(:[])
end
it "should store items internally as a comma separated string" do
co = CustomerOrder.new(:name => "name", :items => [1,2])
co.save!
co.internal_items.should == "1,2"
end
it "should convert items to internal_items for saving" do
co = CustomerOrder.new(:name => "my order",
:items => [1,2])
co.name.should == "my order"
co.save!
co.internal_items.should == "1,2"
end
it "loads items from the database into the items array correctly" do
co = CustomerOrder.new(:name => "woot", :items => [2,1])
co.save.should == true
co2 = CustomerOrder.find_by_name("woot")
co2.items.should == [2,1]
end
it "is not valid with items that don't exist" do
#valid_order.items = [3,2,1]
#valid_order.should_not be_valid
end
it "ensures that items exist to be valid" do
#valid_order.items = [1,2]
#valid_order.should be_valid
end
end
This solution works, although it's probably not the best way to inject a mock into the Inventory Service at runtime. I'll try to do a better job of being more clear in the future.
#!/usr/bin/env ruby
# this is the data I have
#data = {
:student => {
:id => '123477',
:first_name => 'Lazlo',
:last_name =>'Fortunatus',
:email=>'Lazlo#fortunatus.org'
},
:contact_info => {
:telephone=>'1 415 222-2222',
:address => '123 Main St',
:city =>'Beverly Hills',
:state=>'California',
:zip_code=>90210,
:social_security_number =>'111-11-1111'
}
}
class Student
# not fully implemented - this is what I need help on.
def get_id_original
# I need this to return the value #data[:student][:id]
end
def get_city_original
# I need this to return the value #data[:contact_info][:city]
end
end
s = Student.new
# this is the original method
# how can I access the #data variable here I tried #data[:student][:id] doesnt work
# I realize that data is outside of the scope of this method. However, is there any way!
s.get_id_original
# My goal is to have a singleton method that acts exactly like get_id_original,
# but get_id_original doesn't work.
def s.id
get_id_original
end
It can be done!
It didn't at first work because #data is an instance attribute of the top level object, so even though Student is derived from Object the attribute isn't in the new instance.
But you can pass self into s.id, and so then the only thing you need to add is an accessor for the data attribute.
However, that's slightly tricky because attr_reader et al are private class methods so you can't use them directly, and you can't (because it's private) just say self.class.attr_reader, you have to open up Object and do it...with these changes your program works...
#data = { :student => { :id => '123477'} }
class Student
end
s = Student.new
def s.id o
o.data[:student][:id]
#how can I access the #data variable here I tried #data[:student][:id] doesnt work
#I realize that data is outside of the scope of this method. However, is there any way!
end
class Object
attr_reader :data
end
puts s.id self
First off, your id method actually has to go into the class.
You could try something like this:
#data = { :student => { :id => '123477'} }
class Student
attr_accessor :id
def initialize(student)
self.id = student[:id]
end
end
s = Student.new(#data[:student])
puts s.id
#!/usr/bin/ruby
#data = { :student => { :id => '123477', :first_name => 'Lazlo', :last_name =>'Fortunatus', :email=>'Lazlo#fortunatus.org' }, :contact_info => { :telephone=>'1 415 222-2222', :address => '123 Main St', :city =>'Beverly Hills', :state=>'California', :zip_code=>90210, :social_security_number =>'111-11-1111' } }
class Student
def initialize( data )
#data = data
end
def get_id_override
#data[:student][:id]
end
def get_first_name_override
#data[:student][:first_name]
end
def get_last_name_override
#data[:student][:last_name]
end
def get_email_override
#data[:student][:email]
end
def get_telephone_override
#data[:contact_info][:telephone]
end
def get_city_override
#data[:contact_info][:city]
end
def get_state_override
#data[:contact_info][:state]
end
def get_zip_code_override
#data[:contact_info][:zip_code]
end
def get_social_security_number_override
#data[:contact_info][:social_security_number]
end
end
s = Student.new(#data)
def s.id
get_id_override
end
def s.first_name
get_first_name_override
end
def s.last_name
get_last_name_override
end
def s.email
get_email_override
end
def s.contact_info
get_telephone_override
end
def s.city
get_city_override
end
def s.state
get_state_override
end
def s.zipcode
get_zip_code_override
end
def s.ssn
get_social_security_number_override
end
puts s.id
puts s.first_name
puts s.last_name
puts s.email
puts s.contact_info
puts s.city
puts s.state
puts s.zipcode
puts s.ssn
Here is the answer after some work. Anyone has a better answer than mine let me know.
You really should be passing in the data object so it s has its own reference to it.
#data = { :student => { :id => '123477'} }
class Student
attr_accessor :data
def initialize(data)
#data = data
end
end
s = Student.new(#data)
# or you can use s.data = #data
def s.id
#data[:student][:id]
end
puts s.id
A word of caution. Any modifications to #data in the outermost scope will be reflected in s, because both #data variables point to the same object in memory.
But what if you don't want to modify the Student class? You can just add the accessor to s:
#data = { :student => { :id => '123477'} }
class Student
end
s = Student.new
class << s
attr_accessor :data
end
def s.id
#data[:student][:id]
end
s.data = #data
puts s.id
This code does the equivalent of your own answer, with some improvements. (Only by reading that did I realize what you were trying to accomplish.) To avoid being overly complex, I tried to avoid dynamically generating method names.
#!/usr/bin/env ruby
require 'forwardable'
#data = {
:student => {
:id => '123477',
:first_name => 'Lazlo',
:last_name =>'Fortunatus',
:email=>'Lazlo#fortunatus.org'
},
:contact_info => {
:telephone=>'1 415 222-2222',
:address => '123 Main St',
:city =>'Beverly Hills',
:state=>'California',
:zip_code=>90210,
:social_security_number =>'111-11-1111'
}
}
class ContactInfo
def initialize( data )
#data = data
end
def get_telephone_override
#data[:telephone]
end
def get_city_override
#data[:city]
end
def get_state_override
#data[:state]
end
def get_zip_code_override
#data[:zip_code]
end
def get_social_security_number_override
#data[:social_security_number]
end
end
class Student
extend Forwardable # enables delegation (see ruby-doc.org's standard library)
# delegates multiple methods to #contact_info, so they can be called on Student.
# Remember to have the leading colon.
def_delegators :#contact_info,
:get_telephone_override,
:get_city_override,
:get_state_override,
:get_zip_code_override,
:get_social_security_number_override
def initialize( data )
#data = data[:student]
# this is an example of composing objects to achieve separation of concerns.
# we use delegators so ContactInfo methods are available on the Student instance.
#contact_info = ContactInfo.new(data[:contact_info])
end
def get_id_override
#data[:id]
end
def get_first_name_override
#data[:first_name]
end
def get_last_name_override
#data[:last_name]
end
def get_email_override
#data[:email]
end
end
s = Student.new(#data)
class << s
alias_method :id, :get_id_override
alias_method :first_name, :get_first_name_override
alias_method :last_name, :get_last_name_override
alias_method :email, :get_email_override
alias_method :contact_info, :get_telephone_override
alias_method :city, :get_city_override
alias_method :state, :get_state_override
alias_method :zipcode, :get_zip_code_override
alias_method :ssn, :get_social_security_number_override
end
puts s.id
puts s.first_name
puts s.last_name
puts s.email
puts s.contact_info
puts s.city
puts s.state
puts s.zipcode
puts s.ssn
I think your question would've been clearer if you posted the code as you wanted it to work. I'm going to suggest an edit.
Should you be defining an instance variable (prefixed by "#") outside of a class definition?
Also, you can't define a method with a period in the name