Single Table Inheritance in Rails 4 - ruby-on-rails

I'm trying to implement a somewhat simple STI in Rails 4, but there's something I can't yet manage to achieve.
I have the following classes:
class Person < ActiveRecord::Base
end
class NaturalPerson < Person
end
class LegalPerson < Person
end
class Employee < NaturalPerson
end
class Customer < NaturalPerson
end
The thing is, I have some attributes that I want to access only from the Employee class, some only from Customer, etc, but I can't find the way. If I were to be using Rails 3's way I would've solved it with attr_accesible. But this isn't posible now, since I'm neither using the attr_accesible gem, nor I'm willing to.

I woud use different person_params in my controller,
def person_params
params.require(:person).permit(:email, :last_name, :first_name)
end
def natural_person_params
params.require(:person).permit(:email, :job, :location)
end
and create a method where I would test the class name of object or the type attribute as it is a STI) to determine which params to use...
Hope this helps
Cheers

If you're trying to use a single controller for all of the models, then put ALL the attributes into the white listed params.
def person_params
params.require(:person).permit(:email, :last_name, :first_name, :job, :location)
end
If you want to separate them, then you'll want separate params for each type of person:
def person_params
params.require(:person).permit(:email, :last_name, :first_name)
end
def employed_person_params
params.require(:person).permit(:email, :job, :location)
end

Related

Rails validation - confirming presence of form attribute that is not saved in model

My application has a form_for tag with element :foo that is not saved in the model for the object used in form_for.
I need to confirm that the user submitted a value for this element, using Rails Validation Helpers. However, the 'presence' validator makes a call to object.foo to confirm that it has a value. Since foo is not saved as part of my object, how can I do this validation?
Thanks!
You should probably check for the presence of it in the params in your controller action:
def create
#model = MyModel.find(params[:id])
unless params[:foo].present?
#model.errors.add(:foo, "You need more foo")
end
# ...
end
If :foo is an attribute of your object that isn't saved in the database and you really want to use ActiveRecord Validations, you can create an attr_accessor for it, and validate presence like this.
class MyModel < ActiveRecord::Base
attr_accessor :foo
validates :foo, presence: true
end
But that could result in invalid records being saved, so you probably don't want to do it this way.
Try this..
class SearchController < ApplicationController
include ActiveModel::ForbiddenAttributesProtection
def create
# Doesn't have to be an ActiveRecord model
#results = Search.create(search_params)
respond_with #results
end
private
def search_params
# This will ensure that you have :start_time and :end_time, but will allow :foo and :bar
params.require(:val1, :foo).permit(:foo, :bar , whatever else)
end
end
class Search < ActiveRecord::Base
validates :presence_of_foo
private
def presence_of_foo
errors.add(:foo, "should be foo") if (foo.empty?)
end
end
See more here

How to refactor standard methods calls?

Need idea how to refactor the code:
attr_accessor :product
attr_reader :name, :mark
def name=(value)
super unless product.present?
end
def mark=(value)
super unless product.present?
end
... and a whole bunch of method of sorts.
The idea is simple - to deny assigning values if a product is already set. But the code above isn't DRY at all.
Any ideas?
attr_accessor :product
attr_reader :name, :mark
["name", "mark"].each do |method|
define_method("#{method}=") do |value|
super(value) unless product.present?
end
end
You can create a method such as attr_* to handle this. This is done by reopening the Class class and defining the following method.
def attr_validator(*args)
#We simply iterate through each passed in argument...
args.each do |arg|
# Create an attr_reader
self.class_eval("
def #{arg};
##{arg};
end
")
# Here we hardcode "product" since this every attribute
# checks itself against this object
self.class_eval("
def #{arg}=(val);
super unless product.present?;
end
")
end
end
This way, we get rid of the redundancy of appending attributes to the pre-processor method (in my case, attr_validator) and also a different array for metaprogramming purposes.
This can be used thus...
attr_accessor :product
attr_validator :name, :mark, :price, :stock # ...and so on

Tableless model in Rails

I want to create tableless model which doesn't need datebase. At example:
class Post < ActiveRecord::Base
attr_accessible :body, :title, :language_id
belong_to :language
end
class Language
has_many :post
...
end
Will be 2 or 3 language. I don't want to load DB, is it possible to create languges in model by hand?
It might help to read this article: http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/.
In general, your models need not inherit from ActiveRecord, because you can include ActiveModel instead.
On the other hand, you can keep it simple like so:
class Langauge
attr_accessor :posts
def initialize
#posts = []
end
def add_post(post)
#posts << post
end
end
lang = Language.new
lang.add_post(Post.new)

conditional validation in one model but two different forms

I have one model but two different forms ,one form i am saving through create action and another one through student_create action.I want to validate a field in student_create action form and leave other one free.How do i do it?Any help will be appreciated
class BookController < ApplicationController
def create
if #book.save
redirect_to #book #eliminated some of the code for simplicity
end
end
def student_create
if #book.save #eliminated some of the code for simplicity
redirect_to #book
end
end
I have tried this but it didnt work
class Book < ActiveRecord::Base
validates_presence_of :town ,:if=>:student?
def student?
:action=="student_create"
end
end
Also this didnt work
class Book < ActiveRecord::Base
validates_presence_of :town ,:on=>:student_create
end
In the one that should not be validated you do this:
#object = Model.new(params[:xyz])
respond_to do |format|
if #object.save(:validate => false)
#do stuff here
else
#do stuff here
end
end
the save(:validate => false) will skipp the validation.
I was able to acomplish it what i wanted to do by giving it an option :allow_nil=>true
Sounds like you have two types of books. not sure what your domain logic is but the normal flow I would do nothing.
class Book < ActiveRecord::Base
end
Then for the path you want an extra validation you could do this:
class SpecialBook < Book
validates :town, :presence => true
end
If this is the case you might want to consider Single Table Inheritance.
In another case you might want to save the student_id on the book.
Then
class Book < ActiveRecord::Base
validate :validate_town
private
def validate_town
if student_id
self.errors.add(:town, "This book is evil, it needs a town.") if town.blank?
end
end
end

Rails ActiveRecord: validate single attribute

Is there any way I can validate a single attribute in ActiveRecord?
Something like:
ac_object.valid?(attribute_name)
You can implement your own method in your model. Something like this
def valid_attribute?(attribute_name)
self.valid?
self.errors[attribute_name].blank?
end
Or add it to ActiveRecord::Base
Sometimes there are validations that are quite expensive (e.g. validations that need to perform database queries). In that case you need to avoid using valid? because it simply does a lot more than you need.
There is an alternative solution. You can use the validators_on method of ActiveModel::Validations.
validators_on(*attributes) public
List all validators that are being used to validate a specific
attribute.
according to which you can manually validate for the attributes you want
e.g. we only want to validate the title of Post:
class Post < ActiveRecord::Base
validates :body, caps_off: true
validates :body, no_swearing: true
validates :body, spell_check_ok: true
validates presence_of: :title
validates length_of: :title, minimum: 30
end
Where no_swearing and spell_check_ok are complex methods that are extremely expensive.
We can do the following:
def validate_title(a_title)
Post.validators_on(:title).each do |validator|
validator.validate_each(self, :title, a_title)
end
end
which will validate only the title attribute without invoking any other validations.
p = Post.new
p.validate_title("")
p.errors.messages
#=> {:title => ["title can not be empty"]
note
I am not completely confident that we are supposed to use validators_on safely so I would consider handling an exception in a sane way in validates_title.
I wound up building on #xlembouras's answer and added this method to my ApplicationRecord:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def valid_attributes?(*attributes)
attributes.each do |attribute|
self.class.validators_on(attribute).each do |validator|
validator.validate_each(self, attribute, send(attribute))
end
end
errors.none?
end
end
Then I can do stuff like this in a controller:
if #post.valid_attributes?(:title, :date)
render :post_preview
else
render :new
end
Building on #coreyward's answer, I also added a validate_attributes! method:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def valid_attributes?(*attributes)
attributes.each do |attribute|
self.class.validators_on(attribute).each do |validator|
validator.validate_each(self, attribute, send(attribute))
end
end
errors.none?
end
def validate_attributes!(*attributes)
valid_attributes?(*attributes) || raise(ActiveModel::ValidationError.new(self))
end
end

Resources