Ignoring specific validation errors - ruby-on-rails

I have Labellings which belong to Emails and Labels.
Each labelling must be unique to the email/label pair - so an email can only be labelled 'test' once.
I'm doing this with validates_uniqueness_of :label_id, :scope => :email_id. This works as expected.
When I am labelling emails, I want to add the labelling if it is unique, and do nothing if the email is already labelled with that label.
I don't want to duplicate the validation functionality around my app with something like:
email.labels << label unless email.labels.include?(label)
Is it possible to ensure each labelling has a unique email_id/label_id pair without having to check it manually or handle exceptions?

I haven't tested it, but you can probably override << in the association proxy, something like:
class Email < ActiveRecord::Base
has_many :labelings
has_many :labels, :through => :labelings do
def <<(label)
unless proxy_owner.labels.include?(label)
proxy_owner.labelings << Labeling.new(:email => proxy_owner, :label => label)
end
end
end
end

Related

Product Model With Stock_QTY Scoped to Size

I have a functioning, self built e-com web app, but right now the app assumes we have infinite quantity.
It uses line_items and product models.
I am going to add stock_QTY as an attribute to the product
For items that don't have any variants (sizes, colors etc.), the line_item will be created if and stock_QTY is greater than one.
I'm not sure how to deal with sizes though.
Should I create different Products? IE:
Shirt.create (name:"small green shirt", color:"green", size:S, stock_QTY:4)
Shirt.create (name:"medium green shirt", color:"green", size:M, stock_QTY:6)
Shirt.create (name:"large green shirt", color: "green", size:L, stock_QTY:1)
This seems repetitive, but at least the stock QTY can have some independence. Is there a way to create only one shirt record, with variants, and allow them to have different sizes?
Ideally I'd like
Shirt.create(name:"shirt", colors:['red', 'blue', 'green'], sizes: ['s','m',l'])
and then be able to do
Shirt.where(color => "green").where(size => "L").stock_QTY
=> X number
Shirt.where(color => "green").where(size => "M").stock_QTY
=> Y number
This way I have one model, but it can store different quantities depending on the scope of the variants.
Let me know if this is unclear.
Thanks!
Update
Product.rb
require 'file_size_validator'
class Product < ActiveRecord::Base
has_many :line_items
before_destroy :ensure_not_referenced_by_any_line_item
mount_uploader :image, ImageUploader
validates :price, :numericality => {:greater_than_or_equal_to => 0.01}
validates :title, :uniqueness => true
def to_param
"#{id}_#{permalink}"
end
private
# ensure that there are no line items referencing this product
def ensure_not_referenced_by_any_line_item
if line_items.empty?
return true
else
errors.add(:base, 'Line Items present')
return false
end
end
end
Here is my Product as it is now.
from seeds.rb
Product.create!([
{
:title => "Liaisons Shirt Green",
:description => "",
:has_size => true,
:price => 24.99,
:permalink => "shirt",
:weight => 16.00,
:units => 1.00,
:image => (File.open(File.join(Rails.root, "app/assets/images/dev7logo.png")))
}
])
So, my advice is to improve the DB schema to make it more flexible and scalable ;)
Define the Size and Color models (2 new tables), make your actual Product model the BaseProduct model (just renaming the table) and finally create the Product model (new table) which will have 3 external keys (base_product_id, color_id and size_id) and of course the stock_qty field to define all possible configurations with the minimal repetition of information :)!
Just a little help, you're final classes schema should be like:
class Color < ActiveRecord::Base
end
class Size < ActiveRecord::Base
end
class BaseProduct < ActiveRecord::Base
# This will have almost all fields from your actual Product class
end
class Product < ActiveRecord::Base
# Ternary table between Color, Size and BaseProduct
end
I'm omitting all associations because I like the idea you succeed on your own, but if you need, please just ask :)
This will allows you to do BaseProduct queries like:
base_product.colors
base_product.sizes
product.base_product # retrieve the base_product from a product
and to keep trace of the quantities:
product.stock_qty
product.color
product.size # size and color are unique for a specific product
You can also create some helper method to make the creation process similar to the one you'd like to have (as shown in your question).
Well I understand the approaches you wanted to deal with. Pretty easy business logic if I understand correctly. So you wanted the following things If I get you correctly:
You have so many products
You want to add stock count record
You wanted to validate the product for selling (line items for cart) if the product available
You need to ensure if the product is already in customer's cart when you are deleting that.
So I assumed you already added the stock_qty columns.
Now you need to ensure if the product is available to be added in your cart.
So you need to write your validation in your line_item modem.
class LineItem < ActiveRecord::Base
# other business logics are here
belongs_to :product
before_validation :check_if_product_available
def check_if_product_available
# you will find your product from controller, model should be responsible to perform business
# decision on them.
if !self.try(:product).nil? && self.product.stock_qty < 1
errors.add(:product, 'This product is not available in the stock')
return false
end
end
end
This is the approach I believe is the valid way to do. And moreover, rather saving variants in same product model, I would suggest consider designing your model more efficiently with separate variant model or you can utilize the power of self association.
I hope this will help you. Let me know if I miss anything or miss interpret your problem.
Thanks

Ordering a has_many list in view

I have a model called Person that the user selects five personality Traits for. However, the order they pick them for matters (they are choosing most descriptive to least descriptive).
I know how to create a join table with a poison an do ordering that way. I'm using acts_as_list as well.
But I can't seem to find any help on, is how to create a way for the user of my app to set the order of the traits. That is I want to have say five select boxes on in the HTML and have them pick each one, and use something like jQuery UI Sortable to allow them to move them around if they like.
Here is a basic idea of my models (simplified for the purpose of just getting the concept).
class Person < ActiveRecord::Base
has_many :personalizations
has_many :traits, :through => :personalizations, :order => 'personalizations.position'
end
class Personalization < ActiveRecord::Base
belongs_to :person
belongs_to :trait
end
class Trait < ActiveRecord::Base
has_many :persons
has_many :persons, :through => :personalizations
end
I just have no idea how to get positioning working in my view/controller, so that when submitting the form it knows which trait goes where in the list.
After a lot of research I'll post my results up to help someone else encase they need to have list of records attached to a model via many-to-many through relationship with being able to sort the choices in the view.
Ryan Bates has a great screencast on doing sorting with existing records: http://railscasts.com/episodes/147-sortable-lists-revised
However in my case I needed to do sorting before my Person model existed.
I can easily add an association field using builder or simple_form_for makes this even easier. The result will be params contains the attribute trait_ids (since my Person has_many Traits) for each association field:
#view code (very basic example)
<%= simple_form_for #character do |f| %>
<%= (1..5).each do |i| %>
<%= f.association :traits %>
<% end %>
<% end %>
#yaml debug output
trait_ids:
- ''
- '1'
- ''
- '2'
- ''
- '3'
- ''
- '4'
- ''
- '5'
So then the question is will the order of the elements in the DOM be respected whenever the form is submitted. Specially if I implement jQuery UI draggable? I found this Will data order in post form be the same to it in web form? and I agree with the answer. As I suspected, too risky to assume the order will always be preserved. Could lead to a bug down the line even if it works in all browsers now.
Therefore after much looking I've concluded jQuery is a good solution. Along with a virtual attribute in rails to handle the custom output. After a lot of testing I gave up on using acts_as_list for what I am trying to do.
To explain this posted solution a bit. Essentially I cache changes to a virtual property. Then if that cache is set (changes were made) I verify they have selected five traits. For my purposes I am preserving the invalid/null choices so that if validation fails when they go back to the view the order will remain the same (e.g. if they skipped the middle select boxes).
Then an after_save call adds these changes to the database. Any error in after_save is still wrapped in a transaction so if any part were to error out no changes will be made. It was easiest therefore to just delete all the endowments and save the new ones (there might be a better choice here, not sure).
class Person < ActiveRecord::Base
attr_accessible :name, :ordered_traits
has_many :endowments
has_many :traits, :through => :endowments, :order => "endowments.position"
validate :verify_list_of_traits
after_save :save_endowments
def verify_list_of_traits
return true if #trait_cache.nil?
check_list = #trait_cache.compact
if check_list.nil? or check_list.size != 5
errors.add(:ordered_traits, 'must select five traits')
elsif check_list.uniq{|trait| trait.id}.size != 5
errors.add(:ordered_traits, 'traits must be unique')
end
end
def ordered_traits
list = #trait_cache unless #trait_cache.nil?
list ||= self.traits
#preserve the nil (invalid) values with '-1' placeholders
list.map {|trait| trait.nil?? '-1' : trait.id }.join(",")
end
def ordered_traits=(val)
#trait_cache = ids.split(',').map { |id| Trait.find_by_id(id) }
end
def save_endowments
return if #trait_cache.nil?
self.endowments.each { |t| t.destroy }
i = 1
for new_trait in #trait_cache
self.endowments.create!(:trait => new_trait, :position => i)
i += 1
end
end
Then with simple form I add a hidden field
<%= f.hidden :ordered_traits %>
I use jQuery to move the error and hint spans to the correct location inside
the div of five select boxes I build. Then I had a submit event handler on the form and convert the selection from the five text boxes in the order they are in the DOM to an array of comma separated numbers and set the value on the hidden field.
For completeness here is the other classes:
class Trait < ActiveRecord::Base
attr_accessible :title
has_many :endowments
has_many :people, :through => :endowments
end
class Endowment < ActiveRecord::Base
attr_accessible :person, :trait, :position
belongs_to :person
belongs_to :trait
end

Mongoid validation uniqueness with scope and belongs_to

I have the following mongoid model, with a scoped validation to prevent multiple votes on one bill. Each vote belongs to a user and a group:
class Vote
include Mongoid::Document
field :value, :type => Symbol # can be :aye, :nay, :abstain
field :type, :type => Symbol # TODO can delete?
belongs_to :user
belongs_to :polco_group
embedded_in :bill
validates_uniqueness_of :value, :scope => [:polco_group_id, :user_id, :type]
end
The user has the following method to add a vote to a bill:
def vote_on(bill, value)
if my_groups = self.polco_groups # test to make sure the user is a member of a group
my_groups.each do |g|
# TODO see if already voted
bill.votes.create(:value => value, :user_id => self.id, :polco_group_id => g.id, :type => g.type)
end
else
raise "no polco_groups for this user" # #{self.full_name}"
end
end
and a Bill class which embeds many :votes. This is designed to allow a user to associate their vote with different groups ("Ruby Coders", "Women", etc.) and is working well, except the database currently allows a user to vote multiple times on one bill. How can I get the following to work?
u = User.last
b = Bill.last
u.vote_on(b,:nay)
u.vote_on(b,:nay) -> should return a validation error
Most probably validators on Vote are not getting fired. You can confirm that by adding a validates function and outputting something or raising an exception in it.
class Vote
validate :dummy_validator_to_confirmation
def dummy_validator_to_confirmation
raise "What the hell, it is being called, then why my validations are not working?"
end
end
If after creating above validations User#vote_on doesn't raises exception, it confirms that callbacks are not fired for Vote via vote_on method. You need to change your code to fire callbacks on Vote. Probably changing it to resemble following would help:
def vote_on(bill, value)
if my_groups = self.polco_groups # test to make sure the user is a member of a group
my_groups.each do |g|
# TODO see if already voted
vote = bill.votes.new(:value => value, :user_id => self.id, :polco_group_id => g.id, :type => g.type)
vote.save
end
else
raise "no polco_groups for this user" # #{self.full_name}"
end
end
There is an open issue on mongoid github issue tracker to allow cascade callbacks to embedded documents. Right now callbacks are only fired on document on which persistence actions are taking place on.

Is validates_presence_of the preferred technique to require a has_many relationship

Basically: My model requires at least one instance of an associated model be present. Should I use validates_presence_of to assert this validation, or should I write some custom validation code?
Here are the essentials of my model:
class Event < ActiveRecord::Base
has_and_belongs_to_many :channels
validates_presence_of :channels, :message => "can't be empty"
end
(I assume things would be the same if I used has_many in place of has_and_belongs_to_many.)
Instead of the validates_presence_of line I could do this:
def validate
errors.add(:channels, "can't be empty") if channels.size < 1
end
I replaced the latter with the former in the Rails app I'm working on and am wondering if there might be any problems.
So to be more sure, I wrote the following rspec coverage, and both implementations respond the same:
describe Event do
before do
#net = Factory.create(:network)
#net_config = Factory.create(:network_config, :network => #net)
end
it "must have a channel" do
e = Factory.build(:event, :network => #net, :channels => [])
e.should have(1).error_on(:channels)
end
end
That is, if I remove the validation code, the above spec fails; if I put in either version of the validation code, the above spec passes.
So I might assume that my new implementation is ok. But I've read that validates_presence triggers a database load which, in turn, would wipe out any in-memory objects constructed from nested attributes. The proxy_target method, on the other hand, will return the in-memory objects without triggering a load. Some links on proxy_target: http://rubydoc.info/docs/rails/ActiveRecord/Associations/AssociationProxy http://withoutscope.com/2008/8/22/don-t-use-proxy_target-in-ar-association-extensions
In my particular case I'm not using ActiveRecord::Relation, but I wonder if I need to be cautious about this.

In Rails 3 how can you access the user_id of the last user in the database in your model?

Edit See Below:
class Post < ActiveRecord::Base
last_user_id = User.last.id
validates_inclusion_of :user_id, :in => 0..last_user_id
end
The above solution works but as vojlo explains, once in production the code will only be executed once and the model will then validate against an incorrect range of users.
I'm working on a tutorial (rails 3.0.3) and have tried for the last half hour to figure out how to tell rails that one of the classes in my model should make sure the :user_id is within the range zero to the user_id of the last user in the database.
I know I need to be using:
validates_inclusion_of :user_id, :in 0..(can't figure out this piece)
I was able to easily ensure the number entered for User_ID in numeric with:
validates_numericality_of :user_id
I'm looking for information on where to research this, I took a good look at the ActiveRecord Validators documentation and didn't find much there.
What happens if a User deletes their account? Don't you really just want to check for existence of the user?
class Post < ActiveRecord::Base
belongs_to :user
validates_presence_of :user
...
Otherwise it's this pattern, which evaluates the post for validity against your requirements - the user_id is greater than zero and less than the largest user ID currently in the database:
class Post < ActiveRecord::Base
include ActiveModel::Validations
validates_with UserIdValidator
...
class UserIdValidator < ActiveModel::Validator
def validate(record)
max_user = User.find(:one, :order=>["id"])
unless(user_id > max_user.id && user_id > 0)
record.errors[:base] << "This record is invalid"
end
end
end
But I still don't quite understand why you would want to do this - is there something particularly special about your user id? I'd recommend the first approach.
Range from zero to last user id? The only way is writing a new validator.
I don't understand your purpose, but check out validates_associated.
Since you're just doing this for learnings sake:
(A)
Check for "last id" presuming it is the "largest id value":
class Post < ActiveRecord::Base
last_user_id = ActiveRecord::Base.connection.select_one('SELECT MAX(ID) AS "MAX_ID" FROM users')["MAX_ID"]
validates_inclusion_of :user_id, :in => 0..last_user_id
end
or
(B) MAX() will get the max value of the ID field. This may not necessarily be the "latest" record inserted. If you really want to get the "last user inserted" you could do this (checks for the latest time of insertion of the record):
class Post < ActiveRecord::Base
last_user_id = ActiveRecord::Base.connection.select_one('SELECT ID AS "LAST_ID" FROM users WHERE created_at = (SELECT MAX(created_at) from users LIMIT1)')["LAST_ID"]
validates_inclusion_of :user_id, :in => 0..last_user_id
end
Why not use validates_uniquness_of :user_id?
Assuming that the range of 0..the_latest_user_id contains all existing user ids, then you are simply looking to see if it is unique. Determining its uniqueness would be the same thing as determining if it is in that range.

Resources