Rails - Contextual validations based on state - validation

Let's say there is a post model like this:
class Post < ActiveRecord::Base
.......
.......
end
It has two attributes :title and :body.
Now a Post object can go through multiple stages: 'draft' -> 'published'.
Now while saving a post in drafts mode, the :title isn't required. But while saving it in published mode, it needs to have a presence validation on the title:
validates_presence_of :title
Now, what is the best way to do this in Rails? I think some implementation of a decorator pattern would be great, wherein in a controller, I would dynamically add validations to an active record object.
This is a simplified version of a bigger problem I have. In the actual case, there are a lot more validations including those done on associated objects.

If you are using state_machine for your state transitions, it supports just what you are looking for, with examples in the readme file.
Otherwise, all rails validations have optional if paramter. If, for example, your post has a published? method that returns whether it's in a published state, you could write validates_presence_of :title, if: :published? and have it do exactly what you need.

Related

Multiple controllers, two "depths" of relationships

I'm writing my first app in Ruby on Rails (I've only went through railstutorial.org before ) which is simple Electronic Prescription Service and I run on one thing I can't cope with.
I want to create form for adding new prescription as well as adding some medicines which belong to this newly created prescription.
First problem is - how can I actually add medicines to the prescription when during filling the form prescription doesn't exist yet? What I did before was I first create prescription with #user.prescription.create(presc_attributes) and later #prescription.relations.create(medicine_id).
Now I need to do this probably on one form sending the whole thing using one button ( one HTML request, am I right? ) unless you guys have got better idea. I'm also struggling with an idea where to actually put method creating this prescription. Should it be in PrescriptionController#new or maybe connected to RelationsController#new as well?
I've read couple of articles about nested fields_for but they don't seem to match exactly what I need.
Im really new to RoR so sorry if I missed something important.
Here is my EER as well
http://i.stack.imgur.com/sa9CB.png
UPDATE---
ahhh i see, I think what you want is a relationship with an inverse_of.
If you are using a belongs_to on the join model, it is a good idea to set the :inverse_of option on the belongs_to, which will mean that the following example works correctly (where tags is a has_many :through association):
#post = Post.first
#tag = #post.tags.build name: "ruby"
#tag.save
The last line ought to save the through record (a Taggable). This will only work if the :inverse_of is set:
class Taggable < ActiveRecord::Base
belongs_to :post
belongs_to :tag, inverse_of: :taggings
end
If you do not set the :inverse_of record, the association will do its best to match itself up with the correct inverse. Automatic inverse detection only works on has_many, has_one, and belongs_to associations.
FROM http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

How to inform the models of the api version?

Currently, I use api-versions gem which nicely creates api routes and contains the version information in the request HEAD.
However, sometimes the models need to know the api version being used. Example, validations. Let's say presence of username needs to be validated on any version >= 2, but not on versions < 2.
In the Controller layer, it's easy to retrieve the api version by parsing out the appropriate request HEAD. But what's the best way to communicate that version number to the model?
How about defining a psudo-attribute in the model class with validators acting conditionally
class SomeModel < ActiveRecord::Base
attr_accessor :api_version
validates_presence_of :username, :if => :api_version > 2
...
Then from the controller, set the api_version on the model before saving etc
#someModel = SomeModel.new(params[:some_model])
#someModel.api_version = <<extracted from HEAD>>
#someModel.save!

Understanding how ActiveRecord exists? works when building an association

I have two models that are associated via a has_many relationship. E.g.
class Newspaper < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :newspaper
validates :uid, presence: true,
uniqueness: { case_sensitive: true }
end
A newspaper is updated several times a day but we only want to construct and add articles to the association that do not already exist. The following code was my first cut of achieving this.
new_articles.each do |article|
unless newspaper.articles.exists? uid: article.uid
newspaper.articles.build(uid: article.uid)
end
end
The newspaper object is either new and unsaved, or retrieved with existing relationships at this point.
My tests indicate that I am able to add two articles to the newspaper that have the same UID using the code above and this is obviously not want I want.
I appears to me that my current code will result in a validation failure upon being saved as the validation looks at uniqueness across the entire articles table and not the association.
What I'm struggling to understand is how the exists? method behaves in this scenario (and why it's not saving my bacon as I planned). I'm using FactoryGirl to build a newspaper, add an article and then simulate an update containing an article with the same uid as the article I've already added. If the code works I should get only one associated article but instead I get two. Using either build or create makes no difference, thus whether the article record is already present in the database does not appear to change the outcome.
Can anyone shed some light on how I can achieve the desired result or why the exists? method is not doing what I expect?
Thanks
The association exists? actually creates a scoped query, as per the association. This is why your existing articles filter doesn't work.
unless newspaper.articles.exists? uid: article.uid
# `articles.exists?` here will produce this if the newspaper is new
# ... WHERE "articles"."newspaper_id" IS NULL AND "articles.uid" = '<your uid>'
# and this, if the newspaper is persisted (with an id of 1)
# ... WHERE "articles"."newspaper_id" = 1 AND "articles.uid" = '<your uid>'
The case of the new newspaper is clearly wrong, as it would only return articles with a nil newspaper ID. But the persisted case is probably undesirable as well, as it still unnecessarily filters against newspaper ID, when you real concern here is that the UID is unique.
Rather, you probably want simply against Article, rather than scoping the exists? through the association, like:
unless Article.exists? uid: article.uid
Concerning your other problem:
this appears to be a FactoryGirl problem where the create method isn't creating db entries in the same way I can in the irb.
FactoryGirl.create should still abide by validations. It might help to see your test.

overriding password_digest validation in rails 3.1 has_secure_password

So I wish to allow empty password_digest in has_secure_password, but looking at ActiveModel::SecurePassword seems like it is hardcoded:
validates_presence_of :password_digest
Is the only way to override this is by using monkey patching?
I don't think has_secure_password will do what you want, as you discovered.
So instead of using has_secure_password, you could write your own, basically copy the source and 'fork' it to do what you want -- only generate and validate a password if #user is not nil, is what it sounds like you want.
Or, as jtomasri suggests, you could make an Employee model and a User model -- you can use the ActiveRecord 'single table inheritance' feature to have them still both be in the same table, and have a common super-class you can still fetch upon (say, Account.find to return both Employees and Users matching your criteria). In some ways this is the 'cleaner' solution, although it's increasing the complexity of the behind the scenes ActiveRecord stuff -- in the past single table inheritance was a bit buggy, I think they are behaving well these days. (Haven't used it myself in a while).
I just had an related issue and used the :if operator for the validation.
validates :password ,..., :if => :employee_needs_password?
validates :password_confirmation , ... , :if => :employee_needs_password?
def employee_needs_password?
# check whatever your condition is and return true/false
end
why would you include has_secure_password if you want an empty password or why would you implement password's if you accept empty ones. Anyways you can always implement your own digest method without using has_secure_password using BCrypt, there's a Railscast #250 Authentication from scratch where you can see this in action

Validating nested models?

To be more specific, "How do I validate that a model requires at least x valid associated models to be created?". I've been trying to validate nested models that get created in the same form as the parent (and ultimately show immediate validations a la jQuery). As a popular example, lets assume the following models and schema.
class Project
include DataMapper::Resource
property :id, Serial
property :title, String, :nullable => false
has 2..n, :tasks
end
class Task
include DataMapper::Resource
property :id, Serial
property :project_id, Integer, :key => true
property :title, String, :nullable => false
belongs_to :project
end
All the validations are done in the schema definitions, as you can see. The important one here is "has 2..n, :tasks". This validation actually works normally, given that the nested task attributes in the params hash will produce valid tasks. If they produce an invalid task, however, then the task won't get created and you'll end up with a Project that has less than 2 tasks, and thus an invalid project object.
As I understand it, this is because it can't figure out if the task attributes are valid or not until it attempts to save the tasks, and since - as far as I know - the tasks can't get saved before the project, the project is unaware if the tasks will be valid or not. Am I correct in assuming this?
Anyway, I was hoping there would be a quick answer, but it seems a lot less trivial then I'd hoped. If you've got any suggestions at all, that would be greatly appreciated.
I actually found a nice solution here using transactions in DataMapper. Basically this transaction tries to save the parent object as well as all the child objects. As soon as one fails to save, then the transaction stops and nothing gets created. If all goes well, then the objects will save successfully.
class Project
def make
transaction do |trans|
trans.rollback unless save
tasks.each do |task|
unless task.save
trans.rollback
break
end
end
end
end
end
This assures that everything is valid before it anything gets saved. I just needed to change my #save and #update methods to #make in my controller code.
SET CONSTRAINTS DEFERRED might be useful if your database engine supports that.
Otherwise, maybe write a stored procedure to do the inserts, and then say that its the resonsibility of the stored procedure to ensure that only correct, validated data is inserted.
There is a model method valid? that runs the validations on a model object before it is saved. So, the simple way to validate the associations would be to use validates_with_block' or 'validates_with_method to check the validations on the associations.
It would look something like this
validates_with_block do
if #tasks.all?{|t|t.valid?}
true
else
[false, "you have an invalid task"]
end
end
Or you could look at dm-association-validator or dm-accepts-nested-attributes
Edit: for extra crazy. run validations on the tasks, then check to see if the only errors are ones related to the association.
validates_with_block do
if #tasks.all?{|t|t.valid?;!t.errors.any?{|e|e[0]==:project}}
true
else
[false, "you have an invalid task"]
end
end

Resources