Add custom fields to object in ROR application - ruby-on-rails

I'm working on CRM platform.
I would like my users to add, edit and delete custom fields in Client, Contact and Lead objects. Those fields may be plain textfield, list, checkbox, tag etc. Those fields may be required or not. Those fields may have custom validation (that user will define).
Say one company from financials would like to add income to Client object, another would add order configuration to Lead object.
Is there any "enterprise-level" solution (ROR gem) for my problem.
Of cause I know about Custom configuration and config gem, but it doesn't look extensible enough.

Hard question, but this is how I would try to deal with it: I would make all the objects to be derived from a CustomField object, then I would create a one to many relationship between it and a Field model. Something like this:
create_table :field_types do |t|
t.string :name # This would identify the fields: checkbox, plain text, etc
end
create_table :fields do |t|
t.belongs_to :custom_field, null: false, index: true
t.belongs_to :field_type, null: false, index: true
t.string :name
end
class Field < ApplicationRecord
belongs_to :custom_field
belongs_to :field_type
end
class CustomField < ApplicationRecord
has_many :fields
end
This way you could just look into the specified fields on the database and mount it at the view.
Then I would create a table for each type of field that could be used by the users to save the data from the CustomField objects. For instance, I would check the Client field specifier, mount a view with checkboxes A and B. Then, I would get the data from the checkboxes and save each of them at the table Checkboxes with an identifier, so that I could tell that it came from clients.
Depending on what you need to do, another idea that pops to my head is to save the data as a JSON string into the database. This way you could have different fields with different values, all you would need to do is serialize and deserialize to save and load it from the database, respectively.
Sorry if it was a little confusing. Hope it helps.

Assuming your database is relational:
I would suggest to use Entity-Attribute-Value pattern:
https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model.
Here is a gem for it:
https://github.com/iostat/eav_hashes
Also document-oriented database like MongoDB would be an option, if you ever consider changing database. It is schemaless, so you can have different attributes for different instance.

I'm not aware of any out of the box options available, but you might be better off rolling your own on something like this anyway. It will allow you more flexibility, and shouldn't be terrible to implement. In terms of models, I'd probably go with a single-table inheritance table for the fields, probably using a jsonb column for customization options (assuming postgres):
create_table :fields do |t|
t.string :type, null: false # TextField, ListField, etc.
t.jsonb :config, default: {}, null: false
t.belongs_to :contact
end
You can then subclass as necessary for different use-cases:
class Field < ApplicationRecord
belongs_to :contact
end
class TextField < Field
def required=(required)
config[:required] = required
end
end
class CheckboxField < Field
def default_checked=(default_checked)
config[:default_checked] = default_checked
end
end
You can look into something like jsonb_accessor to make for a cleaner interface to the jsonb column.
Likewise, single-table inheritance looks like it may also make sense for the contacts as well, not sure what the base table should be, but maybe something like:
create_table :contacts do |t|
t.string :type, null: false # Contact, Lead, Client
end
class Contact < ApplicationRecord
end
class Lead < Contact
end

Here are some examples I found helpful for custom fields:
http://railscasts.com/episodes/403-dynamic-forms?view=asciicast
And:
https://github.com/lab2023/postgresql_jsonb_ransack_rails_5
https://gist.github.com/ismailakbudak/2ca1feac945999ec3e7d9cf0a373497a

Related

Rails 5.1 - should I create a database index on primary_key and foreign_key?

Here is a migration to create a table:
class CreateTemplates < ActiveRecord::Migration[5.1]
def change
create_table :templates, id: :uuid do |t|
t.references :account, type: :uuid, foreign_key: true
t.string :name
t.text :info
t.string :title
t.timestamps
end
end
end
Since account_id is a foreign_key (and identifies the customer) it will appear in almost all (99%) of queries on this table - there is not much point in retrieving a template that belongs to another customer.
So should I drop the index the above migration created for the account_id foreign_key and create this one instead?
add_index :templates, [:id, :account_id], unique: false
Or should I keep the original and also add this?
EDIT
To clarify the 99% use case - I think I was mistaken there. When creating a template, the account_id is always inserted so that the index method of the tempaltes_controller will always return all templates using the account_id, so that a user only sees a list of templates belonging to their account. For edits, updates, deletes, those actions only need the template_id. So my 99% guess is wrong! Most queries won't actually need a composite key it seems to me.
If most of your queries are going to filter on a combination of [:id, :account_id](which is unlikely) then creating a composite index will improve the performance of your queries.
However, it sounds like that most of your queries will only require :account_id, If that is the case then you do not need to add a composite index.

Adding additional fields on top of an inherited model and expose all super and child class fields

My Goal:
I'm trying to create two different types of users, with different profiles.
Barber, Client, BarberProfile, and ClientProfile
I have my base User object that contains information like email, password, and all the other fields that Devise keeps track of.
I'd like the base User model to have one Profile that keeps track of all basic information that I want all my users to have. For instance, first_name, last_name, avatar, etc.
I'm using single table inheritance to create two different types of users: Client and Barber.
I want each of these types of users to have a base Profile associated with it, and then have additional fields that belong to a BarberProfile and a ClientProfile, respectively.
The BarberProfile will have things that the Barber needs, but the Client doesn't. For instance, a bio. The ClientProfile will have things the Client needs, but the Barber doesn't. For instance, hair_type.
What I currently have, and my problem:
As stated above, I've created a table for User and Profile. So I'm able to call user.profile.first_name. I created a BarberProfile and ClientProfile table in order to add the extra fields.
I'd like to just be able to reference user.profile.bio if the user type is Barber. But bio isn't a part of the base profile. So in this case I'd have to create an associated Profile and and associated BarberProfile to get everything I need. I could just do user.profile.first_name and user.barber_profile.bio, but it feels messy, and I'm making two different associations from essentially the same type of model. I feel like it should be a simple thing to just have the BarberProfile inherit all fields from Profile and add its own specific Barber fields on top.
How does one go about doing this in Rails?
Edit: One of the main reasons I want to do this is so I can update things like first_name and bio within the same form for a Barber. And similarly, a first_name and hair_type within the same form for a Client.
If you want to avoid using two associations on the users for Profile and Client/BarberProfile, I think you should make ClientProfile and BarberProfile extend Profile (single table inheritance) and each of them "has one :barber_profile_data" (I'm not sure how to call it). To fix the long method calls, you can use delegated methods.
class Barber > User
has_one :barber_profile
delegate :bio, to: :barber_profile
class Client < User
has_one :client_profile
delegate :first_name, to: :client_profile
class BarberProfile < Profile
has_one :barber_profile_data
delegate :bio, to: :barber_profile_data
class ClientProfile < Profile
has_one :client_profile_data
delegate :first_name, to: :client_profile_data
Then, when you do "#barber.bio", it should call, internally, "#barber.barber_profile.barber_profile_data.bio".
This sounds like a good use case for Multiple Table Inheritance. In MTI you use additional tables to decorate the base model.
The main advantage to MTI is that the clients and barbers tables can contain the specific columns for that type vs STI which requires you to cram everything into users by design (thats why they call it single table).
create_table "barbers", force: :cascade do |t|
# only the specific attributes
t.text "bio"
end
create_table "clients", force: :cascade do |t|
# only the specific attributes
t.string "hair_type"
end
create_table "users", force: :cascade do |t|
t.string "email"
t.string "first_name"
t.string "last_name"
# ... all the other common attributes
t.integer "actable_id"
t.string "actable_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
This is an example with the ActiveRecord::ActsAs gem.
class User < ApplicationRecord
actable
end
class Barber < ApplicationRecord
acts_as :user
end
class Client < ApplicationRecord
acts_as :user
end
Note that Barber and Client should not be true subclasses. ActiveRecord::ActsAs instead delegates to the "actable" class.
You can then do Barber.all or Client.all to fetch specific types or use User.all.map(:specific) to get decorated users of all types.

Token access to edit function of multiple models

I'm working on an application where a guest will be provided a short Base64 token that they could in turn use to access the edit function of one of several different models via one "search form" on the application homepage.
I have already created the token functionality and included it in the schema for the models I need. My question is, how would one best search for and access the edit function using the access token from the home page?
I'm having a hard time finding a good way to do this and while I'm finding a lot about access tokens, most of it doesn't seem to pertain to my use case.
Rails provides the ability for model classes to be inherited from a parent model class. Then the models can have shared attributes, but also unique ones. In the database all of these model objects are stored in the same table for all classes, so this is called Single Table Inheritance or STI. (Documented here but there are better docs in blog posts out there.)
If you use this approach, then you could search the parent class for all instances to find matching objects/records.
class AccessToken < ActiveRecord::Base
# has attribute access_token, and maybe others
end
class OneAccessibleKind < AccessToken
# may have other attributes
end
class AnotherAccessibleKind < AccessToken
# may have other attributes
end
Your migration would look something like this:
create_table :access_token do |t|
t.string "access_token"
t.string "type"
# add any additional attributes of subclasses
t.timestamps
end
You can then query against the parent class. Note
all_models = AccessToken.where(access_token: 'a-token')
Note that these will all come back as AccessToken objects (i.e. the parent class), but you can inspect the type attribute to see what their base class is.
This may not be the best solution, however, if your classes are mostly different fields because you'll have lots of unused columns. Depending on your backing database (assuming row-oriented SQL) and number of objects this could be a performance problem.
Another option would be to use a one-to-one relationship and have an AccessToken model for each of your other models. Here you can use an STI association.
class AccessToken < ActiveRecord::Base
belongs_to :owner, :polymorphic => true
end
class OneAccessibleKind < ActiveRecord::Base
has_one :access_token, :as => :owner
end
class AnotherAccessibleKind < ActiveRecord::Base
has_one :access_token, :as => :owner
end
With migrations something like this:
create_table :access_token do |t|
t.string :access_token
t.integer :owner_id, null: false
t.string :owner_type, null: false
t.timestamps
end
create_table :one_accessible_kind do |t|
# any attributes for this type
t.timestamps
end
Then you can find an access token and access each owner to get the objects.
AccessToken.where(access_token: 'a-token').map(&:owner)

Rails: unknown attribute error for derived class

I have a model named PaypalPayment:
class PaypalPayment < PaymentMethod
belongs_to :order
def provider_class
PaypalPayment
end
def process!
end
end
I generated the following migrations for it:
class CreatePaypalPayments < ActiveRecord::Migration
def change
create_table :paypal_payments do |t|
t.integer :order_id
t.integer :payment_id
t.timestamps
end
end
end
and
class AddDetailsToPaypalPayment < ActiveRecord::Migration
def change
add_column :paypal_payments, :state, :string
add_column :paypal_payments, :amount, :decimal
add_column :paypal_payments, :cc, :string
add_column :paypal_payments, :cm, :string
end
end
After the migration the table looks something like:
development_database=# select * from paypal_payments;
id | order_id | payment_id | created_at | updated_at | state | amount | cc | cm
But when I try to initialize an object of this model, I'm getting the unknown attribute: payment_id.
#paypal_payment = PaypalPayment.new(:payment_id => params[:tx], :state => params[:st], :cc => params[:cc], :cm => params[:cm], :order_id => params[:id])
EDIT: db/schema.rb:
create_table "paypal_payments", :force => true do |t|
t.integer "order_id"
t.integer "payment_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "state"
t.decimal "amount"
t.string "cc"
t.string "cm"
end
There are different ways to model inheritance in a relational database, Martin Fowler lists the following options:
Single Table Inheritance : all classes are stored in a single table
Class Table Inheritance : all classes have their own table
Concrete Table Inheritance : only concrete classes have a table (e.g. in your example PaymentMethod if being abstract, would not have a table)
Now ActiveRecord only supports STI: single table inheritance.
So if you write
class PaypalPayment < PaymentMethod
ActiveRecord will assume STI and look for a type column, and furthermore, will only look for payment_methods table.
Depending on what you want, in most cases, STI is just perfect. Sometimes I prefer the Class and Concrete Table Inheritance better, but especially for associations this needs a little more householding, since:
e.g. you have different payment-methods, but they are stored in different tables
do you want to access all payment methods at once, you need the "abstract class"
you need an association per possible payment-method
if you have the "abstract class", how do you link to the "real payment method". One way is to include table-name and id of the child as the link.
There are lots of way to solve this, but always harder than using a single table. Also this is stretching the relational datamodel, as depending on the chosen solution, foreign key constraints are not automatically supported. I could go into detail,
but I am not sure if this is relevant, as your example seems a classic case for STI.
If you do want to use Class Table Inheritance or Concrete Table Inheritance, each class has to derive from `ActiveRecord::Base`` and you should include a module (or concern) with the shared behaviour if needed (since ruby does not support multiple inheritance).
I believe you have to add the column "type" to your PaymentMethods table. This will allow it to be inheritable. Without the type column, when you instantiate a PaypalPayment, it thinks it's a PaymentMethod and hence has none of the unique fields of PaypalPayment. However when you add the column "type" to PaymentMethod, then it will store "PaypalPayment" and ActiveRecord knows to make the PaypalPayment methods available. You should probably make a model for PaymentMethod also and make sure it inherits ActiveRecord::Base
def change
add_column :payment_methods, :type, :string
end
Here's some info:
http://www.archonsystems.com/devblog/2011/12/20/rails-single-table-inheritance-with-polymorphic-association/
I'd do this:
Check your Rails Console --
$ rails c
$ payment = PaypalPayment.find(1)
$ payment.column_names #-> should reveal which columns Rails comes back with
Check Rails is picking up the attribute
For testing's sake, just try attr_accessor :payment_id to see if that works. You might not have permitted the attribute in your model
In Rails4, that means using strong params, but in Rails 3, I think it means using attr_accessible like this:
#app/models/paypal_payment.rb
Class PaypalPayment < ActiveRecord::Base
attr_accessible :payment_id #-> tests parameter passing
attr_accessor :payment_id #-> tests virtual attribute assignment
end
I know I'm a bit late the show here, but if anyone is encountering a similar problem with Rails 5.1, in my case I was able to resolve the issue by including the following line in my parent classes
self.abstract_class = true

Single Table Inheritance Ruby on Rails

So within rails, using active record, we can have multiple models that inherit user
Base Class - User
Sub-Class - Employee, Manager, Supervisor
So in rails we use only one table though when you create a new Employee and when you try to access Employee.salary, though only managers and supervisors should have access to those attributes. How do you protect those? Attr methods?
Thanks in Advance.
I set up STI in a project a long time ago. I forget the details now but here is some code from that project that might help. This is from Rails 2.3.4. Not sure what it would look like in Rails 3.
By default Rails with assume any column called type is there for Single Table Inheritance purposes. If you need to specify use the "set_inheritance_column" as you see below.
I don't recall being able to protect the attributes of one subclass from the other subclasses. Like you mentioned, its all stored in the same table. I think I tried at the time and couldn't but I was very new to Rails at the time. I would think it might be doable with attr_accessable or something on the subclass. I might be wrong though but give it a try.
class User < ActiveRecord::Base
set_inheritance_column :user_type
attr_protected :user_type
end
class BusinessOwner < User
has_many :businesses
end
class SiteUser < User
end
From the schema:
create_table "users", :force => true do |t|
t.string "user_type"
t.string "username"
t.string "email"
t.integer "location_id"
...
end

Resources