Apologies in advance - I'm very much a learner, and writing projects as a means to learn. In this one, I'm trying to extend ActiveRecord so that I can do the following...
In my model definition, call ...
attr_special :field, :field
Then, elsewhere, be able to access this list through something like
Model.special_attributes
Probably something really obvious. I'm fine extending ActiveRecord, but I'm not even sure what I'm searching for for guidance beyond this (constructor?)...
You can define something like the code below to create custom DSL in your models:
module SpecialAttributes
module ClassMethods
def attr_special(*attrs)
class_attribute :special_attributes
self.special_attributes = attrs
end
def special_attributes
self.special_attributes
end
end
module InstanceMethods
# some code here
end
def self.included(base)
base.extend ClassMethods
base.send :include, InstanceMethods
end
end
class ActiveRecord::Base
include SpecialAttributes
end
I reopen ActiveRecord::Base class instead of use inheritance because it's more common in Ruby that inheritance.
I like to use submodules called ClassMethods and InstanceMethods in my module and use self.included method to add them in the base class. So you can use 'include MyModule' without have to know if you add instance or class methods.
I hope I can help you.
Related
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...
I'm really new to Rails and I try to setup a module file to be used in the view. So I believe the correct behavior is to define the module as a helper within a controller and voila, it should be working. However, that's not the case for me. Here is the structure.
lib
functions
-- form_manager.rb
form_manager.rb:
Module Functions
Module FormManager
def error_message() ...
end
end
end
users_controller.rb
class UsersController < ApplicationController
helper FormManager
def new ...
Well, the structure is like the above and when I call the error_message from new.html.erb it gives me the error: uninitialized constant UsersController::FormManager.
So, first of all, I know that in rails 3 lib is not automatically loaded. Assuming that it is not mandatory to autoload the lib folder, how can I make this work and what am I missing?
BTW, please don't say that this question is duplicate. I'm telling you I've been searching for this crap for almost 2 days.
Your module is not autoloaded (at least not in 3.2.6). You have to load it explicitly. You can achieve this with the following line of code
# in application.rb
config.autoload_paths += %W(#{config.root}/lib)
You can check your autoload paths with Rails.application.config.autoload_paths. Maybe it's indeed defined for you?
Now you're sure your module gets loaded, you can check it in rails console by calling
> Functions::FormHelper
Now you can't use that module as a view helper by default. Use #included to define the helper when your module gets included. You achieve "lazy evaluation" this way. I think the problem with your code is that the helper method gets called before the module gets included. (somebody should correct me if I'm wrong)
Here's the code:
Module Functions
Module FormManager
def error_message() ...
end
def self.included m
return unless m < ActionController::Base
m.helper_method :error_message
end
end
end
You should also remove the helper line from your controller.
EDIT:
You can achieve this without autoloading. Just use require "functions/form_manager". You define a helper_method for every method. If you wish use all the module methods as helpers use
def self.included m
return unless m < ActionController::Base
m.helper_method self.instance_methods
end
EDIT2:
It appears that you don't need to use self.included. This achieves the same functionality:
class ApplicationController < ActionController::Base
include Functions::FormManager
helper_method Functions::FormManager.instance_methods
end
It appears you are namespacing FormManager inside of Functions which means you would call it by helper Functions::FormManager
Try that
ActiveRecord proxy_owner is now deprecated and the explanation here is very vague on how to change it, so I'm not sure how to use it my case:
http://apidock.com/rails/ActiveRecord/Associations/AssociationProxy
Here is what I'm trying to do:
class Library < ActiveRecord::Base
has_many :books do
def some_method
proxy_owner.author
end
end
end
I get a warning when I run this code that proxy_owner is deprecated:
DEPRECATION WARNING: Calling record.books.proxy_owner is deprecated. Please use record.association(:books).owner instead.
I can replace proxy_owner.author with:
#associaton.owner.author
This works; however, it seems dangerous. Am I missing something here?
I think it's safer to send :owner to proxy_association instead:
class Library < ActiveRecord::Base
has_many :books do
def some_method
proxy_association.owner.author
end
end
end
The use of proxy_association is now mentioned in the documentation:
However, inside the actual extension code, you will not have access to
the record (record.association(:items).owner) as above. In this case, you can access proxy_association.
For example, record.association(:items) and
record.items.proxy_association will return the same object, allowing
you to make calls like proxy_association.owner inside association
extensions.
I have an object that inherits from ActiveRecord, yet it has an attribute that is not persisted in the DB, like:
class Foo < ActiveRecord::Base
attr_accessor :bar
end
I would like to be able to track changes to 'bar', with methods like 'bar_changed?', as provided by ActiveModel Dirty. The problem is that when I try to implement Dirty on this object, as described in the docs, I'm getting an error as both ActiveRecord and ActiveModel have defined define_attribute_methods, but with different number of parameters, so I'm getting an error when trying to invoke define_attribute_methods [:bar].
I have tried aliasing define_attribute_methods before including ActiveModel::Dirty, but with no luck: I get a not defined method error.
Any ideas on how to deal with this? Of course I could write the required methods manually, but I was wondering if it was possible to do using Rails modules, by extending ActiveModel functionality to attributes not handled by ActiveRecord.
I'm using the attribute_will_change! method and things seem to be working fine.
It's a private method defined in active_model/dirty.rb, but ActiveRecord mixes it in all models.
This is what I ended up implementing in my model class:
def bar
#bar ||= init_bar
end
def bar=(value)
attribute_will_change!('bar') if bar != value
#bar = value
end
def bar_changed?
changed.include?('bar')
end
The init_bar method is just used to initialise the attribute. You may or may not need it.
I didn't need to specify any other method (such as define_attribute_methods) or include any modules.
You do have to reimplement some of the methods yourself, but at least the behaviour will be mostly consistent with ActiveModel.
I admit I haven't tested it thoroughly yet, but so far I've encountered no issues.
ActiveRecord has the #attribute method (source) which once invoked from your class will let ActiveModel::Dirty to create methods such as bar_was, bar_changed?, and many others.
Thus you would have to call attribute :bar within any class that extends from ActiveRecord (or ApplicationRecord for most recent versions of Rails) in order to create those helper methods upon bar.
Edit: Note that this approach should not be mixed with attr_accessor :bar
Edit 2: Another note is that unpersisted attributes defined with attribute (eg attribute :bar, :string) will be blown away on save. If you need attrs to hang around after save (as I did), you actually can (carefully) mix with attr_reader, like so:
attr_reader :bar
attribute :bar, :string
def bar=(val)
super
#bar = val
end
I figured out a solution that worked for me...
Save this file as lib/active_record/nonpersisted_attribute_methods.rb: https://gist.github.com/4600209
Then you can do something like this:
require 'active_record/nonpersisted_attribute_methods'
class Foo < ActiveRecord::Base
include ActiveRecord::NonPersistedAttributeMethods
define_nonpersisted_attribute_methods [:bar]
end
foo = Foo.new
foo.bar = 3
foo.bar_changed? # => true
foo.bar_was # => nil
foo.bar_change # => [nil, 3]
foo.changes[:bar] # => [nil, 3]
However, it looks like we get a warning when we do it this way:
DEPRECATION WARNING: You're trying to create an attribute `bar'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc.
So I don't know if this approach will break or be harder in Rails 4...
Write the bar= method yourself and use an instance variable to track changes.
def bar=(value)
#bar_changed = true
#bar = value
end
def bar_changed?
if #bar_changed
#bar_changed = false
return true
else
return false
end
end
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.