Rails class-table inheritance gem, faux-inheritance, and missing methods - ruby-on-rails

I'm working on modifying part of an existing Rails app to use the Class-Table-Inheritance gem (https://github.com/brunofrank/class-table-inheritance). All's well, except that I have defined some instance methods in my superclass -- Person, which all subclasses need to be able to access. For instance, full_name, which returns the concatenated first and last names, or cite_name, which returns the first initial and last name. Since the CTI gem doesn't actually use Ruby inheritance (all subclasses still inherit from ActiveRecord::Base, and the gem does some funky voodoo to link the ActiveRecord fields together), I can't access these methods in the subclasses I've created. Any thoughts on working around this? I'm not interested in STI, but I'm willing to either fork and hack on this particular CTI gem, or look at other solutions.

try http://peterhamilton.github.com/citier, it's based on CITIEsForRAILS, I forked it & put it through rapid development and various other changes. It fixes most, if not all the bugs it had and works so simply.

Just off the top of my head, did you see that in the migration tables of your subclasses you need to have the line:
create_table :videos, :inherits => :product do |t|
and also in your Model.rb files
class Product < ActiveRecord::Base
acts_as_superclass # I'm guessing you might be missing this line??
end
class Book < ActiveRecord::Base
inherits_from :product
end

I think that the brand new CITIEsForRAILS gem (Class Inheritance & Table Inheritance EmbeddingS For RAILS, see https://github.com/altrabio/CITIEsForRAILS ) does exactly what you desire. This gem extends CTI, STI, & Multi Table Inheritance while preserving Ruby class Inheritance.

Related

Why Rails 5 uses ApplicationRecord instead of ActiveRecord::Base?

We know that Rails 5 added ApplicationRecord as an abstract class which was inherited by our models (ActiveRecord).
But basically, I think every technical requirement we do with ApplicationRecord, we can also do with ActiveRecord::Base. For instance:
module MyFeatures
def do_something
puts "Doing something"
end
end
class ApplicationRecord < ActiveRecord::Base
include MyFeatures
self.abstract_class = true
end
So now every model will be attached the behaviors of MyFeatures. But we can also achieve this in Rails 4:
ActiveRecord::Base.include(MyFeatures)
So what is the benefit of using ApplicationRecord, do you think it is necessary to add ApplicationRecord?
While it may seem the same in basic Rails applications, there actually is an important difference once you begin to use rails engines, plugins / gems or direct methods from ActiveRecord::Base.
ActiveRecord::Base.include(MyFeatures) mixes in the features directly into ActiveRecord::Base and it is present there forever for all later uses of ActiveRecord::Base (it cannot be "unmixed") and there is no way to get the original ActiveRecord::Base anymore in any code after the include. This can easily lead to problems if some of the mixed in feature changed the default ActiveRecord behavior or if e.g. two engines / gems tried to include same-named methods.
On the other hand, the ApplicationRecord approach makes the features present only for the classes (models) that inherit from it, other classes, as well as direct use of ActiveRecord::Base stay pristine, uncluttered by the module features.
This is especially important when engines or rails plugins are used as it allows them to separate their own model logic from the main application's model logic which was not possible before ApplicationRecord.
All of this is also nicely described in this blog post and this github comment.
This is to expand on #BoraMa's answer, and to, hopefully, clear up some confusion around ActiveRecord::Base.abstract_class.
ActiveRecord::Base.abstract_class goes back to at least Rails 3.2.0 (http://api.rubyonrails.org/v3.2.0/classes/ActiveRecord/Inheritance/ClassMethods.html), which was released on January 20, 2012.
Rails 4.0.0 improved the documentation: http://api.rubyonrails.org/v4.0.0/classes/ActiveRecord/Inheritance/ClassMethods.html
So, to everyone who thinks ApplicationRecord is radically new, it's not. It is an improvement, not a breaking change. Nothing was added to ActiveRecord::Base to make this work.
I did the same thing on a Rails 4.2.6 project because the models used UUIDs for ids instead of integers, and this required a change to the default ORDER BY. So, instead of using copy-paste or a concern, I went with inheritance using a UuidModel class and self.abstract_class = true.

Rails: list all attributes that can't be mass assigned

I am working through an old legacy project that has recently been updated. The models to the project are in an engine running separate from the controllers and views etc. The engine is running on rails 4.1.6 while the controllers etc are on Rails 3.
This has led to many issues with mass-assignment. I have created a little module that reads the db columns and white lists the attributes for that model. However in a case such as this NewsItem model which has associations and needs to accept attributes for those associations, the module doesn't work.
class Newsitem < ActiveRecord::Base
include MyAttrAccessibleModule
has_and_belongs_to_many :assets
has_and_belongs_to_many :boroughs
has_and_belongs_to_many :venues
I need to add
attr_accessible :asset1_attachment_remove,
:asset1_attachment_title,
:asset2_attachment_title,
:asset3_attachment_title,
:asset4_attachment_title,
:borough_ids,
:venue_ids
But finding all models that require this is a bit of a pain, seeing as there are well over 100.
Is there a way to highlight, find, test, discover in what other models this error might occur in also?
I think what you're looking for could be this:
Object.attributes.keys - Object.accessible_attributes
This should subtract all the whitelisted attributes from all of the available ones.
Thanks to #Stefan Dorunga for his suggestion. It led me in the right direction.
if reflect_on_all_associations(:has_and_belongs_to_many).any?
association = reflect_on_all_associations(:has_and_belongs_to_many)
association.each {|model| attr_accessible model.plural_name.singularize + "_id"}
association.each {|model| attr_accessible model.plural_name}
end
I query the model to see if it has a relationship where the relation id isnt explicitly listed and generate it dynamically.
The only downside is that I have to include the module after all the listed associations. I usually include modules at the top of a class. Whether this is standard practice or not I do not know.

Ruby on Rails changes table names for models

Completely new to Rails, I read that it changes your table names because it makes a bunch of assumptions but I'm working with tables that were created pre-rails and are used in a PHP API so I can't change them.
I have a Class created in Rails that references the existing table and of course I get a sql error of table not found because it appends an s at the end of the table name, so I went ahead and put this in my Class definition:
class BookSubjects2title < ActiveRecord::Base
set_table_name "book_subjects2title"
belongs_to :bookSubjects
end
Supposedly, that should take care of the problem from what I've read. Yet it doesn't, it still keeps trying to use the name with the s at the end and I get an error from the rails console. Is there anything I need to do for Rails to read this new config? Should be dynamic no?
Not sure if this is your only issue but
belongs_to :bookSubjects
should never have an 's', use this instead.
belongs_to :book_subject

Is there a (code-design) reason to not embed classes when designing MongoMapper models?

This question relates to using Ruby on Rails 3 with MongoMapper and EmbeddedDocument. Also, the ManyAssociation.
http://mongomapper.com/documentation/embedded-document.html
The MongoMapper examples (in the link above) show two separate classes:
class Order
include MongoMapper::Document
many :line_items
timestamps!
end
class LineItem
include MongoMapper::EmbeddedDocument
key :name, String
key :quantity, Integer
end
But this pollutes the global namespace with LineItem. Out of context, LineItem for what? And what if I want another model, say WishList, to also have a LineItem set?
So it is possible to embed the LineItem class inside Order, like this:
class Order
include MongoMapper::Document
many :line_items, :class_name => "Order::LineItem"
timestamps!
class LineItem
include MongoMapper::EmbeddedDocument
key :name, String
key :quantity, Integer
end
end
While this may be technically fine (yes?), will I run into design issues later on? Does it just make the code too ugly? Too complex?
Presumably, the existence of this in the Ruby language means someone thinks it's an ok idea?
One thing I always liked about Django is how it uses "apps" to group related model classes (and separate the namespaces). So my code above is also achieving that in Rails.
I don't see any technical problems to either approach. The problem I've run into is that when a class is embedded in it's parent and is also in the same file, I'll forget it's there. So if your namespaces it as Order::LineItem you can make an "order" folder in your "models" folder and put "line_item.rb" in there.
The other trouble is if you want to have a controller for Order::LineItem, you also have to namespace it and put it in a folder, and in the router it will look like:
resource :orders do
resources :line_items, :controller => "order/line_items"
end
Unless you know your app is going to have multiple types of line_items, I'd recommend not namespacing it—you could be over-coding if you did. For example, if you were later to need two types of line_items you might even find that some of your code could be re-used between models—and if you namespaced your first line_item you may find yourself de-namespacing it.
In Python, namespaces are considered a great thing that there should be more of. But when you think about the global namespace of your Rails app, it's not that cluttered. Gem authors are pretty good about keeping all of their classes namespaced in their one gem module, so in your Rails app you're going to have one namespace for each gem you're using (Rails itself is 5 gems, not sure how many global constants though), plus and a bunch of files included by Rails (e.g. SecureRandom). It turns out to be really nice to have those files "just there" and I've found in practice that namespace collisions are rare and you can easily work around them. I've only run into a namespace issue maybe once, but several times I've accidentally defined a "send" method on a model—a much more common problem with similar repercussions.

Avoiding "Single Table Inheritance" in Rails/ActiveRecord and Legacy Schema

I am trying to integrate with a Legacy table that has a column named "type".
Rails will "smartly" assume that whenever we have a 'type' column in a table, then it will try to use Single Table Inheritance.
Is there anyway to avoid this?
(I can't rename the column).
Well, most of the type it really is smart - convention over configuration has some very real benefits. ;-)
Where convention doesn't work, as in your case, there is (probably - at least, I've always found one so far...) a way around it. For legacy schemas there are a couple of possibilities that spring immediately to mind.
Create a single-table view in your legacy database that remaps columns to avoid AR's conventional names. Not much use if you don't have CREATE VIEW permissions on the DB, of course, or if your DB doesn't have views;
Override the use of :type as the STI indicator using set_inheritance_column, thus
class LegacyValue < ActiveRecord::Base
set_inheritance_column 'does_not_have_one'
end
In Rails 4 set_inheritance_column is deprecated and self.inheritance_column should be used instead:
class LegacyValue < ActiveRecord::Base
self.inheritance_column = 'does_not_have_one'
end

Resources