Strange ActiveRecord model behavior - ruby-on-rails

I have model
class Owner < ActiveRecord::Base
attr_accessible :telephone
validates_uniqueness_of :telephone
validates_telephone_number_of :telephone
before_validation :telephone_normalize
end
in rails console
a = Owner.new(:telephone => '949123456')
=> #<Owner id: nil, telephone: "949123456", created_at: nil, updated_at: nil>
1.9.3-p362 :002 > a.valid?
Owner Exists (0.1ms) SELECT 1 AS one FROM "owners" WHERE "owners"."telephone" = '+421949123456' LIMIT 1
=> false
1.9.3-p362 :003 > a
=> #<Owner id: nil, telephone: "421949123456", created_at: nil, updated_at: nil>
The same, when I save unique number:
1.9.3-p362 :006 > a.telephone = '949123457'
=> "949123457"
1.9.3-p362 :007 > a.save
(0.1ms) begin transaction
Owner Exists (0.2ms) SELECT 1 AS one FROM "owners" WHERE "owners"."telephone" = '+421949123457' LIMIT 1
SQL (2.3ms) INSERT INTO "owners" ("created_at", "telephone", "updated_at") VALUES (?, ?, ?) [["created_at", Wed, 16 Jan 2013 11:55:44 UTC +00:00], ["telephone", "421949123457"], ["updated_at", Wed, 16 Jan 2013 11:55:44 UTC +00:00]]
(88.3ms) commit transaction
=> true
Rails (3.2.11) omits '+' in the beginning of number. Type of number is string. It also saves it without plus sign (if it is unique), but when validating, it calls with plus sign.
What am I doing wrong?

It think telephone column in database is integer type. So the string you have passed is out of its range. That's why you have face this problem.

Unfortunetaly there was bug in my validates_telephone_number_of validator. It had modified attribute :-/
Say
> a = 'aaa' # => 'aaa'
> b = a.to_s # => 'aaa'
> b << 'c' # => 'aaac'
> b # => 'aaac'
> a # => 'aaac'
It is needed to use b = a.to_s.dup.

Related

allow_nil: false does not work from rails console

Newbie here, trying to add some rules to a ruby on rails form, specifically I don't want to allow the creation of an item if this has not a name
class Idea < ActiveRecord::Base
mount_uploader :picture, PictureUploader
belongs_to :project
validates :name, presence: true, allow_nil: false
end
Works smoothly if I create a new item from my app, but not happens the same if I create one item from rails console. How can I avoid the creation of an item without name, no matter if this has been created in the app or in the rails console?
The problem is you have to set allow_blank: false instead of allow_nil: false.
In Ruby an empty string is not nil.
"".nil?
#=> false
"".blank?
#=> true
Update your model like this
class Idea < ActiveRecord::Base
mount_uploader :picture, PictureUploader
belongs_to :project
validates :name, presence: true, allow_blank: false
end
If you want know the differences between nil and blank,see this SO post.
Refer these Guides for allow_blank
Try this from console:-
Idea.create(:name => "Something")
Rails console output:-
1.9.3-p385 :005 > c = CabinNumber.create(:name => "Something")
(0.2ms) begin transaction
SQL (1.1ms) INSERT INTO "cabin_numbers" ("created_at", "name", "status", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Sun, 25 May 2014 00:02:04 IST +05:30], ["name", "Something"], ["status", false], ["updated_at", Sun, 25 May 2014 00:02:04 IST +05:30]]
(139.6ms) commit transaction
=> #<CabinNumber id: 11, name: "Something", status: false, created_at: "2014-05-24 18:32:04", updated_at: "2014-05-24 18:32:04">
OR
idea = Idea.new(:name => "hello")
idea.save
Rails console output:-
1.9.3-p385 :007 > c = CabinNumber.new(:name => "hello")
=> #<CabinNumber id: nil, name: "hello", status: false, created_at: nil, updated_at: nil>
1.9.3-p385 :008 > c.save
(0.1ms) begin transaction
SQL (1.0ms) INSERT INTO "cabin_numbers" ("created_at", "name", "status", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Sun, 25 May 2014 00:02:57 IST +05:30], ["name", "hello"], ["status", false], ["updated_at", Sun, 25 May 2014 00:02:57 IST +05:30]]
(155.0ms) commit transaction
=> true
Cannot create if name field is not provided
1.9.3-p385 :003 > c = CabinNumber.create()
(0.2ms) begin transaction
(0.1ms) rollback transaction
=> #<CabinNumber id: nil, name: nil, status: false, created_at: nil, updated_at: nil>

MultiSelect Not Passing Multiple Values

I have a application that allows a user to upload a creative and assign it to multiple weeks
Week Model
class Week < ActiveRecord::Base
has_many :creative_weeks
has_many :creatives, :through => :creative_weeks
end
Creative Model
class Creative < ActiveRecord::Base
has_many :creative_weeks
has_many :weeks, :through => :creative_weeks
mount_uploader :image, CreativeUploader
end
Creative Weeks [Join Table]
class CreativeWeek < ActiveRecord::Base
belongs_to :week
belongs_to :creative
end
I know the association works which allows me to issue a creative to multiple weeks based on my console:
2.0.0p353 :020 > c = Creative.first
Creative Load (0.2ms) SELECT "creatives".* FROM "creatives" ORDER BY "creatives"."id" ASC LIMIT 1
=> #<Creative id: 10, name: "", account_id: 1, week_id: nil, campaign_id: 1, image: "Quakes_2013_DigiOOH_40YR_704x496.jpg", created_at: "2014-02-20 18:13:47", u
pdated_at: "2014-02-20 18:13:47">
2.0.0p353 :021 > c.week_ids
(0.2ms) SELECT "weeks".id FROM "weeks" INNER JOIN "creative_weeks" ON "weeks"."id" = "creative_weeks"."week_id" WHERE "creative_weeks"."creative_id" = ? [["
creative_id", 10]]
=> [3]
2.0.0p353 :022 > c.week_ids = [1, 2, 3]
Week Load (0.2ms) SELECT "weeks".* FROM "weeks" WHERE "weeks"."id" IN (1, 2, 3)
Week Load (0.1ms) SELECT "weeks".* FROM "weeks" INNER JOIN "creative_weeks" ON "weeks"."id" = "creative_weeks"."week_id" WHERE "creative_weeks"."creative_id"
= ? [["creative_id", 10]]
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "creative_weeks" ("created_at", "creative_id", "updated_at", "week_id") VALUES (?, ?, ?, ?) [["created_at", Thu, 20 Feb 2014 18:20:27
UTC +00:00], ["creative_id", 10], ["updated_at", Thu, 20 Feb 2014 18:20:27 UTC +00:00], ["week_id", 1]]
SQL (0.1ms) INSERT INTO "creative_weeks" ("created_at", "creative_id", "updated_at", "week_id") VALUES (?, ?, ?, ?) [["created_at", Thu, 20 Feb 2014 18:20:27
UTC +00:00], ["creative_id", 10], ["updated_at", Thu, 20 Feb 2014 18:20:27 UTC +00:00], ["week_id", 2]]
(1.0ms) commit transaction
=> [1, 2, 3]
2.0.0p353 :023 >
The issue I am having is getting this same functionality to work on the front end. It will only pass in one value, typically the last one chosen
In my form:
<div class="field">
<%= f.collection_select(:week_ids, Week.all, :id, :start_at, {}, multiple: true, name: 'creative[week_ids]') %>
</div>
Can anyone advise me what I am missing?
TIA
Change the select tag's name to this:
name: 'creative[week_ids][]'
That extra '[]' at the end of the name specifies that you want an array of values to be posted.
Using strong parameters you have to specify that the value will be an array:
def your_strong_params
params.require(:creative).permit(week_ids: [])
end

How to validate that payment can never cause invoice amount payable to be less than zero?

I have this class:
class Payment < ActiveRecord::Base
attr_accessible :amount, :invoice_id
belongs_to :invoice
validates :amount, :numericality => { :greater_than => 0, :less_than_or_equal_to => :maximum_amount }
after_save :update_amount_payable
after_destroy :update_amount_payable
private
def maximum_amount
invoice.amount_payable
end
def update_amount_payable
invoice.update_amount_payable
end
end
class Invoice < ActiveRecord::Base
has_many :payments
after_save :update_amount_payable
def update_amount_payable
update_column(:amount_payable_in_cents, new_amount_payable)
end
private
def new_amount_payable
(total - payments.map(&:amount).sum) * 100
end
end
The code above works. But how can I validate that no payment amount can ever cause invoice.amount_payable to be less than 0?
Especially when multiple payments for the same invoice are possible, this turns out to be tricky.
I've been trying to get my head around this for hours, but to no avail. Maybe an after callback to rollback the database can be used here?
Thanks for any help.
One cross-database solution that will work is to use optimistic locking. Essentially, it requires a special lock_version column, that is checked whenever an update is made. If the lock_version at the time an UPDATE is called is different than what the model is expecting, it throws an error noting that something outside of this model caused the record to change (thus invalidating the update). ActiveRecord supports this out of the box, and it will likely suffice for your needs if you don't mind blocking concurrent transactions altogether.
A case that it won't work is where you want to allow concurrent updates. In this case, you'll need to manually check the result during your update:
def update_amount_payable
new_value = new_amount_payable
raise "Payment amounts can't be greater than total invoice amount" if new_value < 0
count = Invoice.where(id: id, amount_payable_in_cents: amount_payable_in_cents).
update_all(amount_payable_in_cents: new_value)
raise ActiveRecord::StaleObjectError.new(self, 'update amount_payable_in_cents') if count != 1
end
private
def new_amount_payable
(total - payments.sum(:amount)) * 100 # get the amount sum from the database
end
I would change the field names. But given the current database schema try the following code:
app/models/invoice.rb
class Invoice < ActiveRecord::Base
has_many :payments
def still_open_amount
self.amount_payable_in_cents - self.payments.sum('amount_in_cents')
end
end
app/models/payment.rb
class Payment < ActiveRecord::Base
belongs_to :invoice
validates :amount_in_cents, :numericality => { :greater_than => 0 }
before_validation :check_all_payments
private
def check_all_payments
if self.new_record?
if (self.invoice.payments.sum('amount_in_cents') + self.amount_in_cents) > self.invoice.amount_payable_in_cents
errors.add(:amount, 'the invoice would be overpaid')
end
else
if (self.invoice.payments.sum('amount_in_cents') - self.amount_in_cents_was + self.amount_in_cents) > self.invoice.amount_payable_in_cents
errors.add(:amount, 'the invoice would be overpaid')
end
end
end
end
This will through a validation error if you try to create a overpaying payment:
~/Desktop/testapp ᐅ rails c
Loading development environment (Rails 4.0.0.beta1)
1.9.3-p286 :001 > i = Invoice.create(amount_payable_in_cents: 100)
(0.1ms) begin transaction
SQL (6.8ms) INSERT INTO "invoices" ("amount_payable_in_cents", "created_at", "updated_at") VALUES (?, ?, ?) [["amount_payable_in_cents", 100], ["created_at", Mon, 13 May 2013 19:23:24 UTC +00:00], ["updated_at", Mon, 13 May 2013 19:23:24 UTC +00:00]]
(0.8ms) commit transaction
=> #<Invoice id: 1, amount_payable_in_cents: 100, created_at: "2013-05-13 19:23:24", updated_at: "2013-05-13 19:23:24">
1.9.3-p286 :003 > p1 = i.payments.create(amount_in_cents: 90)
(0.1ms) begin transaction
Invoice Load (0.2ms) SELECT "invoices".* FROM "invoices" WHERE "invoices"."id" = ? ORDER BY "invoices"."id" ASC LIMIT 1 [["id", 1]]
(0.2ms) SELECT SUM("payments"."amount_in_cents") AS sum_id FROM "payments" WHERE "payments"."invoice_id" = ? [["invoice_id", 1]]
SQL (0.4ms) INSERT INTO "payments" ("amount_in_cents", "created_at", "invoice_id", "updated_at") VALUES (?, ?, ?, ?) [["amount_in_cents", 90], ["created_at", Mon, 13 May 2013 19:24:10 UTC +00:00], ["invoice_id", 1], ["updated_at", Mon, 13 May 2013 19:24:10 UTC +00:00]]
(1.0ms) commit transaction
=> #<Payment id: 1, invoice_id: 1, amount_in_cents: 90, created_at: "2013-05-13 19:24:10", updated_at: "2013-05-13 19:24:10">
1.9.3-p286 :004 > p2 = i.payments.create(amount_in_cents: 20)
(0.1ms) begin transaction
Invoice Load (0.2ms) SELECT "invoices".* FROM "invoices" WHERE "invoices"."id" = ? ORDER BY "invoices"."id" ASC LIMIT 1 [["id", 1]]
(0.1ms) SELECT SUM("payments"."amount_in_cents") AS sum_id FROM "payments" WHERE "payments"."invoice_id" = ? [["invoice_id", 1]]
(0.1ms) commit transaction
=> #<Payment id: nil, invoice_id: 1, amount_in_cents: 20, created_at: nil, updated_at: nil>
1.9.3-p286 :005 > p2.errors
=> #<ActiveModel::Errors:0x007fd57b8e36d8 #base=#<Payment id: nil, invoice_id: 1, amount_in_cents: 20, created_at: nil, updated_at: nil>, #messages={:amount=>["the invoice would be overpaid"]}>
1.9.3-p286 :006 >

Rails Engines - is it possible to add associations to the model in a container model like Forem does

This Question is more than a single question so breaking it up into more managable pieces: Rails Engines - simple possible engine to (1) add a model and (2) add the association in the containing class
I am testing out building a Rails engine and am curious whether I can add an association to a specific model in the hosting / container app.
The hosting app has a user model class (yes, this will never chnage) and my engine is called abc and I have a model in my engine called posts (so Abc::Post and the table is abc_posts). I'd like to add to the User class in the main app this association. As a drop dead simple try, I created in my engine:
#located in the engine at: abc/app/models/user.rb
class User < ActiveRecord::Base
has_many :abc_posts
end
the post file:
#located in the engine at: abc/app/models/abc/post.rb
module Abc
class Post < ActiveRecord::Base
attr_accessible :body, :header, :user_id
belongs_to :user
end
end
Via rails console, I was able to create records in the table (easy part) but the User class doesn't know about the association. Any ideas on how to get this done?
thx in advance
edit 1
I've tried using the decorators gem as used in forem (see comment below) and have this file:
#abc/app/decorators/lib/abc/user_class_decorator.rb
Object.const_get(User).class_eval do
has_many :abc_posts, :class_name => "Abc::Post", :foreign_key => "user_id"
end
I have included the decorators via:
lib/abc.rb
require "decorators"
but his doesn't seem to be working. Not sure if this is right strategy or whether syntax is even correct.
That should do the job - specify the class for the relationship:
class User < ActiveRecord::Base
has_many :posts, :class_name => "Abc::Post"
end
Hmmm, I created an example and it does work ...
class Parent < ActiveRecord::Base
has_many :children, :class_name => "Abc::Child"
end
The module with the class Child is in the model/abc.
module Abc
class Child < ActiveRecord::Base
belongs_to :parent
end
end
Here the journal
1.9.3-p194 :001 > Parent.create(:name => 'Mr Daddy')
(0.1ms) begin transaction
SQL (9.4ms) INSERT INTO "parents" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", Fri, 03 May 2013 10:49:54 UTC +00:00], ["name", "Mr Daddy"], ["updated_at", Fri, 03 May 2013 10:49:54 UTC +00:00]]
(1.9ms) commit transaction
=> #<Parent id: 1, name: "Mr Daddy", created_at: "2013-05-03 10:49:54", updated_at: "2013-05-03 10:49:54">
1.9.3-p194 :002 > Abc::Child.create(:name => 'Sammy boy', :parent => Parent.first )
Parent Load (0.3ms) SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
(0.1ms) begin transaction
SQL (117.3ms) INSERT INTO "children" ("created_at", "name", "parent_id", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Fri, 03 May 2013 10:49:58 UTC +00:00], ["name", "Sammy boy"], ["parent_id", 1], ["updated_at", Fri, 03 May 2013 10:49:58 UTC +00:00]]
(2.1ms) commit transaction
=> #<Abc::Child id: 1, name: "Sammy boy", parent_id: 1, created_at: "2013-05-03 10:49:58", updated_at: "2013-05-03 10:49:58">
1.9.3-p194 :003 > Abc::Child.create(:name => 'Milly girl', :parent => Parent.first )
Parent Load (0.3ms) SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
(0.2ms) begin transaction
SQL (0.8ms) INSERT INTO "children" ("created_at", "name", "parent_id", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Fri, 03 May 2013 10:50:15 UTC +00:00], ["name", "Milly girl"], ["parent_id", 1], ["updated_at", Fri, 03 May 2013 10:50:15 UTC +00:00]]
(2.7ms) commit transaction
=> #<Abc::Child id: 2, name: "Milly girl", parent_id: 1, created_at: "2013-05-03 10:50:15", updated_at: "2013-05-03 10:50:15">
1.9.3-p194 :004 > Parent.first.children.first
Parent Load (0.4ms) SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
Abc::Child Load (0.3ms) SELECT "children".* FROM "children" WHERE "children"."parent_id" = ? ORDER BY "children"."id" ASC LIMIT 1 [["parent_id", 1]]
=> #<Abc::Child id: 1, name: "Sammy boy", parent_id: 1, created_at: "2013-05-03 10:49:58", updated_at: "2013-05-03 10:49:58">
1.9.3-p194 :005 > Parent.first.children.last
Parent Load (0.5ms) SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
Abc::Child Load (0.4ms) SELECT "children".* FROM "children" WHERE "children"."parent_id" = ? ORDER BY "children"."id" DESC LIMIT 1 [["parent_id", 1]]
=> #<Abc::Child id: 2, name: "Milly girl", parent_id: 1, created_at: "2013-05-03 10:50:15", updated_at: "2013-05-03 10:50:15">
1.9.3-p194 :006 > Parent.first.children.count
Parent Load (0.3ms) SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
(0.3ms) SELECT COUNT(*) FROM "children" WHERE "children"."parent_id" = ? [["parent_id", 1]]
=> 2

how to override model validation messages in rails 3

I have a model,group_question_answer.rb
class GroupQuestionAnswer < ActiveRecord::Base
belongs_to :group_question
validates_presence_of :answer
validates_presence_of :answer_question
end
for attribute answer and answer_question i get error message as Group question answers answer can't be blank
I need to show only answer cant be blank.i even tried adding :message=>"cant be blank",but still i dont get my required message.how can i remove model name and can just arrtibute error message ....
class GroupQuestionAnswer < ActiveRecord::Base
attr_accessible :answer
validate do |group_question_answer|
errors.add(:base, "answer can't be blank") if group_question_answer.answer.blank?
end
end
works perfectly
rails c
Loading development environment (Rails 3.2.9)
irb(main):001:0> q = GroupQuestionAnswer.create
(0.1ms) begin transaction
(0.1ms) rollback transaction
=> #<GroupQuestionAnswer id: nil, answer: nil, created_at: nil, updated_at: nil>
irb(main):002:0> q
=> #<GroupQuestionAnswer id: nil, answer: nil, created_at: nil, updated_at: nil>
irb(main):003:0> q.save
(0.1ms) begin transaction
(0.1ms) rollback transaction
=> false
irb(main):004:0> q.errors
=> #<ActiveModel::Errors:0x007fc2fb325fa8 #base=#<GroupQuestionAnswer id: nil, answer: nil, created_at: nil, updated_at: nil>, #messages={:base=>["answer can't be blank"]}>
irb(main):006:0> q.errors.messages
=> {:base=>["answer can't be blank"]}
=> {:base=>["answer can't be blank"]}
irb(main):007:0> q = GroupQuestionAnswer.create(answer: "123")
(0.1ms) begin transaction
SQL (9.0ms) INSERT INTO "group_question_answers" ("answer", "created_at", "updated_at") VALUES (?, ?, ?) [["answer", "123"], ["created_at", Fri, 28 Dec 2012 11:01:38 UTC +00:00], ["updated_at", Fri, 28 Dec 2012 11:01:38 UTC +00:00]]
(1.1ms) commit transaction
=> #<GroupQuestionAnswer id: 1, answer: "123", created_at: "2012-12-28 11:01:38", updated_at: "2012-12-28 11:01:38">
irb(main):008:0> q.errors.messages
=> {}
In my opinion validate method perfect way to fully customize rails validations and that does exactly what you ask for.
You can try like this:
validates :answer, presence: { message: '<Your message>'}
validates :answer_question, presence: { message: '<Your message>'}
Try adding them in your config/locales/en.yml file
As there you can do something like this,
en:
errors:
messages:
answer: answer can't be blank

Resources