Is Subclassing a User Model Really Bad to Do in Rails

Is subclassing a User model really bad to do in Rails?

Generally speaking, inheritance is discouraged in Ruby in favor of mixin behavior and delegation. Ruby and Rails can do that stuff but it tends to result in the push back you mentioned

Your particular example sounds like a case for delegation: have a User class which belongs to an employee (or vice versa). The type-specific behavior of that employee (eg director, instructor, etc) is all in that specific employee class. Then the user will delegate how to handle specific scenarios to the employee that it's joined with

Should I should subclass this Rails model?

The best way is to determine which functionality you require for each class. If you only need a small amount of changes, then stick to a single class with an enum:

#app/models/coupon.rb
class Coupon < ActiveRecord::Base
enum type: [:percent, :money]

def value
if type.percent?
# ...
elsif type.money?
# ...
end
end
end

This will allow you to use the type in your instance methods, which shouldn't cause such a problem if you didn't have a lot of changes to make within the class.

This would allow you to call:

@coupon = Coupon.find x
@coupon.value #-> returns value based on the type

--

The alternative (STI) would be more of a structured change, and would only work if you were referencing each class explicitly:

#app/models/coupon.rb
class Coupon < ActiveRecord::Base
end

#app/models/percent.rb
class Percent < Coupon
def amount
# ...
end
end

#app/models/money.rb
class Money < Coupon
def takeout
# ...
end
end

An important factor here is how you call these.

For the above classes, you have to reference the subclassed classes on their own:

@percentage_coupon = Percent.find x
@money_coupon = Money.find y

This will obviously be more cumbersome, and may even cause problems with your routes & controllers etc.

.... so it may be best going with the single class :)

Database and relationship design when subclassing a model

I think the two approaches (easily) available to you in Rails are:

1) Single Table Inheritance: You create a single TaskTarget table that has every field that every subclass might want. You then also add a "type" field that stores the class name, and Rails will pretty much do the rest for you. See the ActiveRecord api docs for more info, especially the "Single Table Inheritance" section.

2) Concrete Table Inheritance: There is no table for the base TaskTarget class. Instead, simply create a table for each concrete class in your hierarchy with only the fields needed by that class.

The first option makes it easier to do things like "Show me all the TaskTargets, regardless of subclass," and results in fewer tables. It does make it a little harder to tell exactly what one subclass can do, as opposed to another, and if you have a lot of TaskTargets, I suppose eventually having them all in one table could be a performance concern.

The second option makes for a cleaner schema that is somewhat easier to read, and each class will work pretty much just like any normal ActiveRecord model. However, joining across all TaskTarget tables can be cumbersome, especially as you add more subclasses in the future. Implementing any necessary polymorphic associations may also involve some extra complexity.

Which option is better in your situation will depend on what operations you need to implement, and the characteristics of your data set.

Multiple devise models vs permission based

So a friend of mine solved this very elegantly for me, for everyones reference:

Good question. It’s a great problem that deals with the intersection between good engineering (model implementation, database design) and user experience (single sign in form).

Assuming that Employees and Employers differ enough, it makes sense to implement them as separate models. But it also makes sense to have a single sign in form—employees and employers shouldn’t have to care that they’re signing into the right form.

Single table inheritence usually appears to be the ideal solution, but tends to best be avoided in Ruby on Rails applications unless absolutely necessary.

I’ve actually thought about this problem before, so I would suggest an implementation along these lines:

  • An Employer model.
  • An Employee model.
  • A SignIn/Login/Credentials/WhateverYouWantToCallIt model.

In terms of employer/employee associations, as before:

  • Employee belongs_to :employer
  • Employer has_many :employees

Now, considering that both models are able to sign in, it makes sense to separate these credentials into their own SignIn model. If you do some reading up on polymorphic associations (http://guides.rubyonrails.org/association_basics.html#polymorphic-associations), you’ll find that they are awesome for creating relationships where the association can be with different models.

So now you need to create associations between sign in credentials and employers and employees:

  • SignIn :belongs_to :signinable, polymorphic: true
  • Employer has_one :sign_in, as: :signinable
  • Employee has_one :sign_in, as: :signinable

The elegance of this solution (in my opinion), is that you’re able to separate your SignIn, Employer and Employee models, which not only conforms to good Ruby on Rails conventions, but is good database normalisation practice. At the same time, you have a SignIn model that makes it trivial to implement a better sign in form experience that allows both employers and employees to sign in.

In Rails tutorial, how can Article subclass from ApplicationRecord without 'require_relative application_record'?

The key thing to understand is that imports (requires) in Ruby are global.

Under the hood Rails is requiring application record and then requiring your model files. Since application record was required already (albeit in a different file), the models, and all other bits of code loaded thereafter, have access to it.

You can see this in the following simple example:

# a.rb
require 'b'
require 'c'

# b.rb
class ThisIsExported; end

# c.rb
puts defined?(ThisisExported) # => 'constant'

Remember that classes, modules, and constants are all globals (although they can be namespaced), and that these are the only things 'exported' from a file (well, naked methods can also be exported, but these are actually defined on a global Object instance). So, requiring a file in Ruby is not so much 'importing' it to a specific file, but rather like calling 'eval' on that file's source code.

You can think of it as as if Ruby is just find-and-replacing all require calls with that file's actual source code ... this isn't actally what's happening but the effect is the same.

The closest thing Ruby has to strictly file-specific imports are refinements, but you still need to package those up into a global anyway, and the behaviour is not entirely consistent with regular imports (for example defined? will not be accurate)

# a.rb
require 'b'
require 'c'
require 'd'

# b.rb
module FileSpecificModuleInclude
refine(Object) do
def patched; 'ok'; end
end
end

# c.rb
using FileSpecificModuleInclude
# It says it's not defined ....
puts defined?(patched) # => nil
# But it actually is ...
puts patched # => 'ok'

# d.rb
patched # => NoMethodError

Rails - Skip rails validation for subclass

It sounds like you should abstract out User and Contacts into two tables instead of trying to consolidate them into one. Although contacts can become users, that doesn't mean that they will (I think?).

This would also solve your validate_presence_of :email question, as the contact table/model wouldn't even have the field. It would also alleviate potential performance concerns later on, I believe. You wouldn't want to have a ton of contacts to sort through to find a registered user.

If you're dead-set on doing it in one table though, I believe you can do something like the following:

validates_presence_of :email, :unless => Proc.new {|user| user.type == "Contact"}

This is assuming that you have a user_type column, but you could replace that depending on how you're determining whether a User is a Contact.

Update:

This is how you'd correctly validate the models: Remove the validates_presence_of from the model and place it inside of this block:

with_options :unless => :user_type == "contact" do |user|
user.validates_presence_of :email
end

rails model subclassing - multi table inheritance or polymorphism

The "official" way to achieve this in Rails is to use Single Table Inheritance. Support for STI is built into ActiveRecord: http://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance

If you want to use Multi Table Inheritance you would have to implement it by yourself...



Related Topics



Leave a reply



Submit