avoiding code duplication in Rails 3 models - ruby-on-rails-3

I'm working on a Rails 3.1 application where there are a number of different enum-like models that are stored in the database. There is a lot of identical code in these models, as well as in the associated controllers and views. I've solved the code duplication for the controllers and views via a shared parent controller class and the new view/layout inheritance that's part of Rails 3.
Now I'm trying to solve the code duplication in the models, and I'm stuck. An example of one of my enum models is as follows:
class Format < ActiveRecord::Base
has_and_belongs_to_many :videos
attr_accessible :name
validates :name, presence: true, length: { maximum: 20 }
before_destroy :verify_no_linked_videos
def verify_no_linked_videos
unless self.videos.empty?
self.errors[:base] << "Couldn't delete format with associated videos."
raise ActiveRecord::RecordInvalid.new self
end
end
end
I have four or five other classes with nearly identical code (the association declaration being the only difference). I've tried creating a module with the shared code that they all include (which seems like the Ruby Way), but much of the duplicate code relies on ActiveRecord, so the methods I'm trying to use in the module (validate, attr_accessible, etc.) aren't available. I know about ActiveModel, but that doesn't get me all the way there.
I've also tried creating a common, non-persistent parent class that subclasses ActiveRecord::Base, but all of the code I've seen to accomplish this assumes that you won't have subclasses of your non-persistent class that do persist.
Any suggestions for how best to avoid duplicating these identical lines of code across many different enum models?

I found a solution to the code sharing for Rails 3 models, so thought I'd share it with others. It turns out ActiveModel does have everything I need (so far, at least). I created an Enum module using ActiveSupport::Concern, ActiveModel::Validations, and ActiveModel::MassAssignmentSecurity, and I include the module in each of my enum models:
module Enum
extend ActiveSupport::Concern
include ActiveModel::Validations
include ActiveModel::MassAssignmentSecurity
included do
attr_accessible :name
validates :name, presence: true, length: { maximum: 20 }
before_destroy :verify_no_linked_videos
private
def verify_no_linked_videos
unless self.videos.empty?
self.errors[:base] << "Couldn't delete object with associated videos."
raise ActiveRecord::RecordInvalid.new self
end
end
end
end
The way the Rails 3 team pulled out the non-database code from ActiveRecord into ActiveModel really is pretty slick! The following links helped solidify my understanding of how to use this stuff:
http://www.fakingfantastic.com/2010/09/20/concerning-yourself-with-active-support-concern/
http://asciicasts.com/episodes/237-dynamic-attr-accessible

Related

uninitialized constant in rails?

So I've built a new controller = "Categories_controller.rb" and a new Model = "Category.rb" and now I would normally take my Savedfriend.rb model and use it with Category.rb model like so;
<%= category.savedfriends.size %>
However this time around I keep getting;
uninitialized constant Category::Savedfriend
It's driving me crazy. I do have models all set with belongs_to.
By Rails convention, if you haven't specified your class_name on the association, it is going to look for a singularized, camelized version of the association name for the class name. If, for instance, you have a model SavedFriend, your association should be named saved_friends. If it can't find the class for the association, Rails tends to look for a scoped class within the class that's trying to call it. The error is a little obscure, but I've seen it plenty of times when I have a typo in my associations.
# in app/models/saved_friends.rb
class SavedFriend < ActiveRecord::Base
belongs_to :category
end
# in app/models/category.rb
class Category < ActiveRecord::Base
has_many :saved_friends
end
Also, if your naming scheme for files and classes is as sporadic as it is in your question, you're going to have a bad time. File names should be lowercase and underscored, class names should be a camelized version of the file name. i.e. Categories_controller.rb should be categories_controller.rb, and the class should be CategoriesController. Similarly, saved_friend.rb should contain class SavedFriend.

How Do I Add Methods To ActiveRecord::Base?

I'm trying to create a customized ActiveRecord::Base that includes additional metadata about the connection. I see two ways to go about this:
1.) Inherit from ActiveRecord::Base and add methods & fields in this subclass.
2.) Encapsulate an ActiveRecord::Base object inside my own class
1 has all kinds of problems with the inability to override initialize, weird problems where it doesn't seem to have custom methods I've added, etc.
undefined method `set_profile' for #<Class:0xf041f0>
2 I have not been able to figure out, due to problems with using ActiveRecord::Base.new
I am trying to make an all-purpose ActiveRecord class that I can dynamically establish_connection & set_table_name on, (i.e. not have one underlying table that this ActiveRecord::Base represents) but I can't seem to find a way to accomplish it. Any ideas?
This works:
class MyTable < ActiveRecord::Base
establish_connection $config['custom-db-config'];
set_table_name 'MY_TABLE'
end
but I need a class I can call these things on repeatedly.
Not entirely sure why you'll want that, but maybe you can try this?
module ActiveRecord
class Base
def self.your_method
# implementation goes here
end
end
end
You will need to save this file and put it in config/intializers.
You can also extend the ActiveRecord::Base class in order to add the those methods dynamically which are directly callable by the class inheriting the ActiveRecord::Base...Many acts_as plugins are defined and made according to this practice...

Associated records via custom query in Rails

I have two models that are connected via a has_many/belongs_to association:
Class Project < ActiveRecord::Base
has_many :tasks
end
Class Tasks < ActiveRecord::Base
belongs_to :project
end
Each of the tasks are tagged with a HABTM relationship:
Class Tasks < ActiveRecord::Base
belongs_to :project
has_and_belongs_to_many :tags
end
I am trying to get a list of projects based on a tag id. I can get a list of projects that have tasks with a specific tag by using a class method on my Project model:
def by_tag(tag_id)
Project.joins(:tasks => :tags).where(:tags => {:id = tag_id})
end
Ideally, I'm looking to be able to list all the projects and their associated tasks for a given tag in my view. I could normally get a list of tasks belonging to a given project by using project.tasks if I used a typical find with project like Project.find(1).
However, when I try project.tasks on results found using my new class method Project.by_tag(1), I get a "NoMethodError: Undefined Method 'tasks'" error.
I looked into Named Scopes to get the Project by Tag results but it seems like people are moving away from that approach in favor of class methods. Is that true?
On your project model you need to add it to the class not the instance. Also note that this raises the self object to the class so you can eliminate "Project." unless you want to be explicit.
class << self
def by_tag(tag_id)
joins(:tasks => :tags).where(:tags => {:id = tag_id})
end
end
There is always debate over what is the best method. I myself prefer whatever gets the job done quicker. I like scopes personally but to each his own.

Rails 3 namespacing requires model to be defined twice?

I'm pulling my hair out trying to understand namespacing in Rails 3. I've tried following a few different tutorials, and the only way I can get my models to work is if I define my model in both the base directory and my namespace directory.
If I only define the model in the namespace directory it expects it to define both Model and Namespace::Model, as below:
LoadError (Expected .../app/models/plugins/chat.rb to define Chat):
or
LoadError (Expected .../app/models/plugins/chat.rb to define Plugins::Chat):
I'm sure I'm missing something obvious, but I could really use a pointer in the right direction.
Here are the relevant excerpts.
/models/plugins/chat.rb
class Plugins::Chat
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
...
end
/controllers/plugins/chats_controller.rb
class Plugins::ChatsController < Plugins::ApplicationController
load_and_authorize_resource
...
end
/config/routes.rb
namespace :plugins do
resources :chats
end
/config/application.rb
config.autoload_paths += Dir["#{config.root}/app/models/**/"]
Edit
This is some kind of bad interaction with CanCan, the gem we're using for permissions. The line load_and_authorize_resource is somehow at fault. Will keep digging...
I noticed a reference to load_and_authorize_resource in your controller. This method is used by the CanCan gem to create an instance of your model and then test if the user has access to it. If you are using a namespaced model you will need to specify the class:
class Plugins::ChatsController < Plugins::ApplicationController
load_and_authorize_resource :class "Plugins::Chat"
...
end
It sounds like at some point you're referencing the Chat constant \by itself before it's loaded. Rails then tries to find that by looking at models/chat.rb, can't find it, and complains. Check your constant usage (the backtrace should tell you where it's being invoked from), and clean it up, and Rails should be less complain-y.

Rails: Make different references to a DB row refer to the same Ruby object

Suppose I have the following model relationship:
class Player < ActiveRecord::Base
has_many :cards
end
class Card < ActiveRecord::Base
belongs_to :player
end
I know from this question that Rails will return me a copy of the object representing a database row, meaning that:
p = Player.find(:first)
c = p.cards[0]
c.player.object_id == p.object_id # => false
...and therefore if the Player model modifies self, and the Card model modifies self.player in the same request, then the modifications won't take any notice of each other and the last-saved one will overwrite the others.
I'd like to work around this (presumably with some form of caching), so that all requests for a Player with a given id would return the same object (identical object_ids), thereby allowing both models to edit the same object without having to perform a database save-and-reload. I have three questions:
Is there already a plugin or gem to do this?
Are there good reasons why I shouldn't do this?
Can anyone give me some pointers on how to go about doing this?
This is supported in Rails 3.x. You can use the :inverse_of option for the has_many association for example. Documentation here (search for :inverse_of and Bi-directional associations).