Specify unique attributes for child models in rails using STI
Asked Answered
B

2

6

I plan to use STI in Rails with the following models:

class Promo < ActiveRecord::Base
end

class Event < Promo
end

class Discount < Promo
end

There are just a couple of differences in terms of attributes between Event and Discount so I figure STI is a good way to go.

I'm not sure how to ensure that, for example, only Event has an additional image_filename attribute. I understand that it will be in the promos table, and that it needs to be NULL-able in case I insert a Discount row. How do I ensure that a Discount object knows nothing about the image_filename attribute (i.e. is not listed in Discount.column_names and/or can't set it) and that Event knows about it?

Bicknell answered 11/12, 2014 at 15:46 Comment(4)
I think is not possible that a child class doesn't know about its parent class, but what you can do is write the validations in the child classesCovenantee
@Covenantee maybe I don't understand, but I don't want Discount class to know about its sibling's (Event) image_filename attributeBicknell
yes, but in your promo table you have a column image_filename, the attribute in reality is a property of Promo, other thing is where you use itCovenantee
@Covenantee so trying to set up STI with varying attributes seems like it's not very clean. Looks like I'll look more into polymorphism. If there's no way to do this (what you say does make sense), then I can mark our discussion as the answer if you want to summarize?Bicknell
C
4

I think that the concept is different, while your Promo class inherit from ActiveRecord::Base then all child classes inherit its attributes, so, the column names are shared in all the STI. but you still can set the column image_filename as null in the DB and validate it in your models, so you will be able to store and update the record based in your case with something like this:

class Promo < ActiveRecord::Base
  #Common validations
  validates :name, presence: true
  validates :user, presence: true
end

class Event < Promo
  #event related validations
  validates image_filename, presence: true
end

class Discount < Promo
  #discount related validations
  validates percentage, presence: true
end

Here is a great tutorial http://samurails.com/tutorial/single-table-inheritance-with-rails-4-part-1/

Covenantee answered 11/12, 2014 at 16:18 Comment(2)
I think the type of validation that would be best here to "force" image_filename not to be used with Discount is validates :image_filename, absence: trueBicknell
Agree with you, I was only show you an example of how implement this kind of validationsCovenantee
M
1

Having STI setup means you'll have 2 different forms for each child Class. That means you'll have fields available in the form accordingly for Event and for Discount, so when you'll save a Discount record the second image_file field will have a nil value by default (if you didn't changed it to something else).

Now, all attributes of your Promo class are actually methods that you can override, so if in Discount you'll do:

def additional_image_filename #name of the attribute
  nil
end

it will always return nil as result no matter what it was saved there. An example in console based on my app where I override the name attribute (not that I need, just showing):

2.1.5 :028 > Recipe.last
  Recipe Load (0.2ms)  SELECT  "recipes".* FROM "recipes"   ORDER BY "recipes"."id" DESC LIMIT 1
 => #<Recipe id: 4, name: "Potatoes Au Gratin", instructions: nil, created_at: "2014-12-04 12:54:26", updated_at: "2014-12-04 12:54:26"> 
2.1.5 :029 > Recipe.last.name
  Recipe Load (0.4ms)  SELECT  "recipes".* FROM "recipes"   ORDER BY "recipes"."id" DESC LIMIT 1
 => nil 

as you can see there is a name in database and it has a value, but it returns nil cause it was overridden.

Also you could add validations for separate Classes or callbacks that will make sure to clean up the attributes you don't need like the so called additional_image_filename, but I don't see why you'd need this.. cause for each form you'll have separate controllers and actions I guess and therefore there will be different permitted_params that will let you choose what fields should allowed to be saved only.

In short:

  1. Don't include additional_image_filename in form for Discount
  2. Don't include additional_image_filename in peritted_params in controller action for Discount
  3. Set the value as nil in db or override the method in Discount class as described above.
Mensa answered 11/12, 2014 at 16:20 Comment(4)
I'm not sure what you mean by an additional image_filename column as Discount and Event both share the same field in the database.Bicknell
by additional image_filename I mean the one that you mention you have for event and not for discount, I don't know what's the name of it. additional image_filename is just a placeholderMensa
and so what if they share? depending on how you set up the permitted_params and form fields that field will be accessible only for the class you want to.Mensa
Understood, referring to it as the "second image_filename" and additional_image_filename threw me off. Overriding the attribute method hides that the data isn't clean, but that is definitely a trick I did not know about!Bicknell

© 2022 - 2024 — McMap. All rights reserved.