Problems while making a generic model in Ruby on Rails 3
Why not simply create an ActiveRecord::Base
subclass at runtime and avoid all the hassle?
t = 'some_table'
c = Class.new(ActiveRecord::Base) { self.table_name = t }
then c
refers to an AR class for some_table
and you can do the usual things:o = c.find(1)
# 'o' is now a wrapper for the row of some_table where 'id = 1'
cols = c.columns.map(&:name)
# 'cols' is now an array of some_table's column names
This is Ruby where classes are objects too.If you need to connect to another database then you can to put the establish_connection
call in the block along with the self.table_name
:
t = 'some_table'
d = 'some_other_database'
c = Class.new(ActiveRecord::Base) do
establish_connection(:adapter => 'mysql2', :database => d, ...)
self.table_name = t
end
How to localize a generic error messages partial in Rails 3.2.3?
OK, I found the answer myself this time, thanks to this thread.
I simply changed my _error_messages.html.rb
partial to:
<% if object.errors.any? %>
<div id="error_explanation">
<h3><%= t('errors.template.header', :model => object.class.model_name.human, :count => object.errors.count) %></h3>
<p><%= t('errors.template.body') %></p>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Now it works and I am happy :-) Creating and storing generic methods in ruby on rails
Answers to your question will necessarily be subjective because there are always be many answers to "where should I put functionality?", according to preference, principle, habit, customs, etc. I'll list a few and describe them, maybe add some of my personal opinions, but you'll ultimately have to choose and accept the consequences.
Note: I'll commonly refer to the common degenerate case of "losing namespacing scope" or "as bad as having global methods".
Monkeypatch/Extend String
Convenient and very "OO-message-passing" style at the cost of globally affecting all String
in your application. That cost can be large because doing so breaks an implicit boundary between Ruby core and your application and it also scatters a component of "your application" in an external place. The functionality will have global scope and at worst will unintentionally interact with other things it shouldn't.
Worthy mention: Ruby has a Refinements feature that allows you to do do "scoped monkeypatching".
Worthy mention 2: Ruby also lets you includes
modules into existing classes, like String.class_eval { include MyCustomization }
which is slightly better because it's easier to tell a customization has been made and where it was introduced: "foo".method(:custom_method).owner
will reveal it. Normal Monkeypatching will make it as if the method was defined on String
itself.
Utils Module
Commonly done in all programming languages, a Util
module is simply a single namespace where class methods/static methods are dumped. This is always an option to avoid the global pollution, but if Util
ends up getting used everywhere anyways and it gets filled to the brim with unrelated methods, then the value of namespacing is lost. Having a method in a Util
module tends to signify not enough thought was put into organizing code, since without maintenance, at it's worst, it's not much better than having global methods.
Private Method
Suppose you only need it in one class -- then it's easy to just put it into one private method. What if you need it in many classes? Should you make it a private method in a base class? If the functionality is inherent to the class, something associated with the class's identity, then Yes. Used correctly, the fact that this message exists is made invisible to components outside of that class.
However, this has the same downfall as the Rails Helper module when used incorrectly. If the next added feature requires that functionality, you'll be tempted to add the new feature to the class in order to have access to it. In this way the class's scope grows over time, eventually becoming near-global in your application.
Helper Module
Many Rails devs would suggest to put almost all of these utility methods inside rails Helper modules. Helper modules are kind of in between Utils Module and Private Method options. Helpers are included and have access to private members like Private Methods, and they suggest independence like Utils Modules (but do not guarantee it). Because of these properties, they tend to end up appearing everywhere, losing namespacing, and they end up accessing each other's private members, losing independence. This means it's more powerful, but can easily become much worse than either free-standing class/static methods or private methods.
Create a Class
If all the cases above degenerate into a "global scope", what if we forcibly create a new, smaller scope by way of a new class? The new class's purpose will be only to take data in and transform it on request on the way out. This is the common wisdom of "creating many, small classes", as small classes will have smaller scopes and will be easier to handle.
Unfortunately, taking this strategy too far will result in having too many tiny components, each of which do almost nothing useful by themselves. You avoid the ball of mud, but you end up with a chunky soup where every tiny thing is connected to every other tiny thing. It's just as complicated as having global methods all interconnected with each other, and you're not much better off.
Meta-Option: Refactor
Given the options above all have the same degenerate case, you may think there's no hope and everything will always eventually become horribly global -- Not True! It's important to understand they all degenerate in different ways.
Perhaps functionality 1, 2, 3, 4... 20 as Util methods are a complete mess, but they work cohesively as functionality A.1 ~ A.20 within the single class A. Perhaps class B is a complete mess and works better broken apart into one Util method and two private methods in class C.
Your lofty goal as an engineer will be to organize your application in a configuration that avoids all these degenerate cases for every bit of functionality in the system, making the system as a whole only as complex as necessary.
My advice
I don't have full context of your domain, and you probably won't be able to communicate that easily in a SO question anyways, so I can't be certain what'll work best for you.
However, I'll point out that it's generally easier to combine things than it is to break them apart. I generally advise starting with class/static methods. Put it in Util
and move it to a better namespace later (Printer
?). Perhaps in the future you'll discover many of these individual methods frequently operate on the same inputs, passing the same data back and forth between them -- this may be a good candidate for a class. This is often easier than starting off with a class or inheriting other class and trying to break functionality apart, later.
Generic model that has many relationship depending on value of enum
Your idea of using a enum won't work here since assocations are class level and the value of the enum is only known on the instance level.
If you really wanted to use an enum you could hack something together with an instance method but it won't really behave like an assocation when it comes to stuff like eager loading:
class Blog < ApplicationRecord
# ...
def posts
send("#{platform}_posts")
end
end
What you can do is use Single Table Inheritance to setup classes that share a table yet have different behavior.First add a type
column to the table:
class AddDetailsToBlogs < ActiveRecord::Migration[6.0]
def change
change_table :blogs do |t|
t.remove :platform
t.string :type, index: true, null: false
end
end
end
If you have existing data you should go through it and set the type
column based on the value of platform
before you drop platform
and make type
non-nullable.Then setup the subclasses:
class Blog < ApplicationRecord
# shared behavior
end
class WordPressBlog < Blog
has_many :posts,
class_name: 'WordPressPost',
foreign_key: :blog_id,
inverse_of: :blog
end
class DrupalBlog < Blog
has_many :posts,
class_name: 'DrupalPost',
foreign_key: :blog_id,
inverse_of: :blog
end
The main advantage of STI is that it lets you query as a single table and thus treat it as a homogenous collection, the drawbacks are that you are potentially wasting database space with columns containing largely nulls and it can become quite unweildy if the types differ to much from each other. Generics Example Question
The first thing to realize about the scaffolding code is that it can be abreviated, as such:
def index
@users = User.all
end
unless you intend to deliver the view in another format, like json, html, pdf, the respond_to block is unnecessary. If you still feel the need to dry up this method, you could do something like# app/controllers/concerns/autoload_records.rb
module AutoloadRecords
included do
before_action :load_records, only: :index
before_action :load_record, only: [:create, :show, :edit, :update, :destroy]
end
private
def load_records
@records = model_class.all
end
def load_record
@record = model_class.find(params[:id])
end
def model_class
klass = self.class.to_s[/\A(\w+)sController\Z/,1] #=> get the name of the class from the controller Constant
Object.const_get(klass)
end
end
and write your controller likeclass UsersController < ApplicationController
include AutoloadRecords
def index
@records # => #<ActiveRecord::Relation[...]>
end
def show
@record # => #<User ...>
end
def non_rest_action
@record # => nil
@records # => nil
end
end
Rails 3 - Concerns with Modules or Classes
If your model is Alert
, you definitely don't want module Alert
(#3). #1 and #2 are basically the same, but more often you see the #2 style.
Let me explain a little further.
The module X::Y
style will only work if X
has already been defined. It's saying "create this module Y
under X
and I don't care if X
is a class or module, just do it.
For #3, since Alert
is already defined as a class
, you'll get this error: TypeError: Alert is not a module
.
Let me know if you need more clarification.
Ruby on Rails: Assigning attribute values to generic model
assign_attributes
expects a hash of attributes to be passed to it. You are passing it a string. Would it be problematic to simply say b = {attr_name.to_sym => 9}?
Generic flags for a model in RoR
In my opinion Promotion should be a separate model with a many to many relationship with User. When you have a promotion you would create a Promotion instance and when a person uses that promotion you add that person to promotion.users relationship.
This is much better than your idea because you can now query those relationship. Want a list of all users that used the first quarter promotion? No problem. You can do that with your solution, but you have to resort to some hackiness (is that a word?) to do it, and you'd have to parse the generic flag string for EVERY user on EVERY query. Not ideal to say the least.
Related Topics
Thor Executable - Ignore Task Name
How to Open File in Default Application. Ruby
Rails Gem Prawn, Image and Anchor
Can't Log into Active Admin. Any Way to Create an Admin User
Best Way to Create a Blog with Static Pages in Ruby
Require Tree in Asset Pipeline
What's The Best Way to Test Delayed_Job Chains with Rspec
Sidekiq to Execute at Specific Time in Every Timezones
How to Use Omniauth to Make Authenticated Calls to Services
Paperclip and Amazon S3 How to Do Paths
Statically Compile Pdftk for Heroku. Need to Split PDF into Single Page Files
How to Handle Single Table Inheritance in Simpleform So a Single Helper Handles All Models