I want to include errors from a rather deep assocation in a parent:
class Order < ActiveRecord::Base
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :product, polymorphic: true
end
class Site < ActiveRecord::Base
has_one :line_item, as: :product, autosave: true
validates :domain, presence: true
end
Used as:
product = Site.new(domain: nil)
order = Order.new
order.line_items << LineItem.new(product: product)
order.valid? #=> false
product.valid? #=> false
product.errors? #=> { 'domain' => 'cannot be blank' }
Is there some rails way, or association-parameter to make the errors
bubble up so that I get:
order.errors #=> { 'domain' => 'cannot be blank' }
In other words, that the Order, the top of the association,
transparently proxies the validation errors from its children?
I am aware of using simple before_validation hooks, like so:
class Order < ActiveRecord::Base
before_validation :add_errors_from_line_items
private
def add_errors_from_line_items
self.line_items.each do |line_item|
line_item.product.errors.each do |field, message|
errors.add(field, message)
end unless line_item.product.valid?
end
end
end
end
But I am wondering if there is not some ActiveRecord feature that I am overlooking.
Related
I have a model called Option.
class Option < ApplicationRecord
belongs_to :user
belongs_to :company
belongs_to :scheme
validate :check_for_quantity
def check_for_quantity
if self.quantity > self.scheme.remaining_options
errors.add(:quantity, "cannot be more than the remaining options #{ self.scheme.remaining_options.to_i}")
end
end
end
and a model called Scheme.
class Scheme < ApplicationRecord
belongs_to :share_class
belongs_to :equity_pool
belongs_to :company
has_many :options, dependent: :destroy
attr_accessor :percentage
def ownership
self.remaining_options * 100 / self.company.total_fdsc
end
def remaining_options
self.initial_size - self.options.sum(&:quantity)
end
end
My spec for Option Model looks like this
require 'rails_helper'
RSpec.describe Option, type: :model do
describe "Associations" do
subject { create (:option) }
it { is_expected.to belong_to(:scheme) }
it { is_expected.to belong_to(:vesting_schedule).optional }
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:company) }
end
end
When I run this spec the first example gives an error
1) Option Associations is expected to belong to scheme required: true
Failure/Error: if self.quantity > self.scheme.remaining_options
NoMethodError:
undefined method `remaining_options' for nil:NilClass
# ./app/models/option.rb:9:in `check_for_quantity'
What is the problem here?
My options factory bot
FactoryBot.define do
factory :option do
security "MyString"
board_status false
board_approval_date "2018-08-16"
grant_date "2018-08-16"
expiration_date "2018-08-16"
quantity 1
exercise_price 1.5
vesting_start_date "2018-08-16"
vesting_schedule nil
scheme
user
company
end
end
Just add a condition to the validation so that it is not fired if the association is nil.
class Option < ApplicationRecord
belongs_to :user
belongs_to :company
belongs_to :scheme
validate :check_for_quantity, unless: -> { self.scheme.nil? }
def check_for_quantity
if self.quantity > self.scheme.remaining_options
errors.add(:quantity, "cannot be more than the remaining options #{ self.scheme.remaining_options.to_i}")
end
end
end
You may also want to ensure that self.quantity is a number and not nil to avoid NoMethodError: undefined method > for nil:NilClass which you can do with a numericality validation.
class Option < ApplicationRecord
belongs_to :user
belongs_to :company
belongs_to :scheme
validates_numericality_of :quantity
validate :check_for_quantity, if: -> { self.scheme && self.quantity }
def check_for_quantity
if self.quantity > self.scheme.remaining_options
errors.add(:quantity, "cannot be more than the remaining options #{ self.scheme.remaining_options.to_i}")
end
end
end
Hi I need help and all insight appreciated. I have two models Auctions and Bids and I want to retrieve the All auctions current_user won, the ones s/he has been outbid on and the ones s/he's winning
Here are the two models:
class Auction < ActiveRecord::Base
extend FriendlyId
friendly_id :guid, use: :slugged
before_save :populate_guid
mount_uploaders :images, ImageUploader
belongs_to :client
has_many :bids, dependent: :destroy
has_one :order, dependent: :destroy
validates_presence_of :title, :lien_price,
:end_time, :collateral_value,
:redemption_date, :current_interest_rate,
:additional_tax, :collateral_details,
:location, :client_id, :starting_bid
validate :end_time_in_the_future, :on => :update
validates_uniqueness_of :guid, case_sensitive: false
def end_time_in_the_future
errors.add(:end_time, "can't be in the past") if self.end_time && self.end_time < Time.now
end
def self.get_active_auctions
where("end_time > ?", Time.now)
end
def self.closed_auctions
where("end_time < ?", Time.now)
end
def highest_bid
self.bids.maximum("amount")
end
def highest_bid_object
self.bids.order(:amount => :desc).limit(1).first
end
def highest_bidder
self.highest_bid_object.user if highest_bid_object
end
def closed?
self.end_time < Time.now
end
private
def populate_guid
if new_record?
while !valid? || self.guid.nil?
self.guid = SecureRandom.random_number(1_000_000_000).to_s(36)
end
end
end
end
and
class Bid < ActiveRecord::Base
extend FriendlyId
friendly_id :guid, use: :slugged
belongs_to :auction
belongs_to :user
before_save :populate_guid
validates_presence_of :amount, :user_id,
:auction_id
#validate :higher_than_current?
validates :amount, :numericality => true
validates_uniqueness_of :guid, case_sensitive: false
def higher_than_current?
if !Bid.where("amount > ? AND auction_id = ?", amount, self.auction.id).empty?
errors.add(:amount, "is too low! It can't be lower than the current bid, sorry.")
end
end
private
def populate_guid
if new_record?
while !valid? || self.guid.nil?
self.guid = SecureRandom.random_number(1_000_000_000).to_s(36)
end
end
end
end
I thought
#auctions = Auction.closed_auctions.where(highest_bidder: current_user)
or
#auctions = Auction.closed_auctions.joins(:bids).where(highest_bidder: current_user)
would work but they both raise an error.
Edit this works
#auctions = Auction.closed_auctions.references(highest_bidder: current_user)
But there's probably a better way.
You probably can't access current_user from controller (devise?). So you need to pass the user as a parameter to the class or instance method. What you should look into are scopes and especially scopes that accept parameters. Scopes could really help you refactor your Auction model (you really don't need any methods that only return a where()), but also solve the inaccessible current_user.
Use it like this in your Auction model:
scope: :highest_bidder -> (current_user) { where(highest_bidder: current_user) }
And call it like this from your controller:
#auctions = Auction.closed_auctions.highest_bidder(current_user)
My Page model look like this:
class Page < ActiveRecord::Base
has_many :blocks
accepts_nested_attributes_for :blocks, allow_destroy: true
rails_admin do
edit do
fields :title, :slug, :blocks
end
end
end
My Block model look like this:
class Block < ActiveRecord::Base
belongs_to :page
rails_admin do
edit do
field :title
field :body, :ck_editor
end
end
end
I needed workflow like this:
As an admin I click create page and I should see opened new block section with prefield title.
How can I create this scenario?
My own answer is realy dearty, but it works for me:
class Page < ActiveRecord::Base
has_many :blocks
accepts_nested_attributes_for :blocks, allow_destroy: true
rails_admin do
edit do
fields :title, :slug
field :blocks do
# It is needed to show nested form
active true
end
end
end
# It is needed to create default block with title "main"
after_initialize do
if self.blocks.empty? && self.new_record?
self.blocks << Block.new(title: 'main')
end
end
# It is needed to prevent create default block when form has errors
after_validation do
return if(self.persisted? || self.blocks.empty?)
destroy_array = []
self.blocks.each do |block|
destroy_array << block if block.title == 'main' && block.body.nil?
end
self.blocks.destroy(destroy_array)
end
end
I need a has_many association that has at least two entries, how do I write the validation and how could this be tested using RSpec + factory-girl? This is what I got till now, but it fails with ActiveRecord::RecordInvalid: Validation failed: Bars can't be blank and I'm completely stuck on the RSpec test.
/example_app/app/models/foo.rb
class Foo < ActiveRecord::Base
has_many :bars
validates :bars, :presence => true, :length => { :minimum => 2}
end
/example_app/app/models/bar.rb
class Bar < ActiveRecord::Base
belongs_to :foo
validates :bar, :presence => true
end
/example-app/spec/factories/foo.rb
FactoryGirl.define do
factory :foo do
after(:create) do |foo|
FactoryGirl.create_list(:bar, 2, foo: foo)
end
end
end
/example-app/spec/factories/bar.rb
FactoryGirl.define do
factory :bar do
foo
end
end
class Foo < ActiveRecord::Base
validate :must_have_two_bars
private
def must_have_two_bars
# if you allow bars to be destroyed through the association you may need to do extra validation here of the count
errors.add(:bars, :too_short, :count => 2) if bars.size < 2
end
end
it "should validate the presence of bars" do
FactoryGirl.build(:foo, :bars => []).should have_at_least(1).error_on(:bars)
end
it "should validate that there are at least two bars" do
foo = FactoryGirl.build(:foo)
foo.bars.push FactoryGirl.build(:bar, :foo => nil)
foo.should have_at_least(1).error_on(:bar)
end
You want to use a custom validator
class Foo < ActiveRecord::Base
has_many :bars
validate :validates_number_of_bars
private
def validates_number_of_bars
if bars.size < 2
errors[:base] << "Need at least 2 bars"
end
end
end
I'm having this model:
class Vote
include Mongoid::Document
include Mongoid::Timestamps
field :vote, :type=>Integer
embedded_in :voteable, :inverse_of => :votes
referenced_in :user
attr_accessible :vote, :user, :voteable
validates :vote,:inclusion => [-1, 1]
validates :user ,:presence=> true,:uniqueness=>true
end
The problem is that the validation for user uniqueness per vote is not working, and the same user can create several votes, which is not what I want. Any ideas how to solve that?
Looks like this is a known problem.
http://groups.google.com/group/mongoid/browse_thread/thread/e319b50d87327292/14ab7fe39337418a?lnk=gst&q=validates#14ab7fe39337418a
https://github.com/mongoid/mongoid/issuesearch?state=open&q=validates#issue/373
It is possible to write a custom validation to enforce uniqueness. Here is a quick test:
class UserUniquenessValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << "value #{value} is not unique" unless is_unique_within_votes(record, attribute, value)
end
def is_unique_within_votes(vote, attribute, value)
vote.voteable.votes.each do |sibling|
return false if sibling != vote && vote.user == sibling.user
end
true
end
end
class Vote
...
validates :user ,:presence => true, :user_uniqueness => true
end