Grab original method/column name from Rails alias_attribute - ruby-on-rails

Right now, I have some legacy classes with differently-named columns that I've aliased to a new, common name via Rails' alias_attribute, as below:
class User < ActiveRecord::Base
alias_attribute :id, :UserId
...
end
class Car < ActiveRecord::Base
alias_attribute :id, :CarId
...
end
For some logging purposes, I need to access the old column names (eg. CarId and UserId). Is there a general way to access the old name from alias_attribute via its alias? Renaming the old columns is not ideal, since many other parts of the app are still using the old column names.

alias_attribute is a very simple method. All it does is, well, define aliases.
def alias_attribute(new_name, old_name)
# The following reader methods use an explicit `self` receiver in order to
# support aliases that start with an uppercase letter. Otherwise, they would
# be resolved as constants instead.
module_eval <<-STR, __FILE__, __LINE__ + 1
def #{new_name}; self.#{old_name}; end # def subject; self.title; end
def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end
STR
end
So no, there's no way to retrieve the original column name.

Related

Guidelines regarding spaces for class variables, instance variables, etc

Is there a guideline to follow in regard to space for class variables, instance variables, etc? For example
class MyModel < ApplicationRecord
belongs_to :something
has_many: :something_elses
validates: :property, presence: true
after_save :do_something
end
In this case I'm using as example a model record, but I would like to understand the standard style for everything. I'm using Rubocop and it doesn't tell me anything about this.
Thanks.
anothermh shared the link above (and for the record, amazing that their profile photo is of Robocop when this question is about Rubocop) ... but here's what the Rubocop guidelines for classes suggest:
class Person
# extend and include go first
extend SomeModule
include AnotherModule
# inner classes
CustomError = Class.new(StandardError)
# constants are next
SOME_CONSTANT = 20
# afterwards we have attribute macros
attr_reader :name
# followed by other macros (if any)
validates :name
# public class methods are next in line
def self.some_method
end
# initialization goes between class methods and other instance methods
def initialize
end
# followed by other public instance methods
def some_method
end
# protected and private methods are grouped near the end
protected
def some_protected_method
end
private
def some_private_method
end
end
As a personal note: although having consistent styling makes it quicker and easier to read code and for others to scan what you're written, keep in mind that these are just recommendations of "best practices". At the end of the day whatever works best FOR YOU should be your new best practice.

Making virtual attributes be part of one Hash

I have a model which has one actual column in the database. This column is stored as a JSON string of configuration. I use a bunch of virtual attributes which I want to map inside of this configuration JSON attribute. I basically dont want to create a bunch columns in the db, but rather use this one JSON attribute to contain everything. Is there a cleaner way than the below defs of achieving this?
class Device < ActiveRecord::Base
attr_accessible :configuration
serialize :configuration, JSON
attr_accessor :background_color, :title
# below is ew
def background_color; self.configuration["background_color"]; end
def background_color=(value); self.configuration["background_color"] = value; end
def title; self.configuration["title"]; end
def title=(value); self.configuration["title"] = value; end
end
Ideally i'd be looking for something like attr_maps_to_hash :configuration, [:background_color, :title]. Does something like this exist?
You can use ActiveRecord::Store for this.
class User < ActiveRecord::Base
store :settings, accessors: [ :color, :homepage ]
end
u = User.new(color: 'black', homepage: '37signals.com')
u.color # Accessor stored attribute
u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
# Add additional accessors to an existing store through store_accessor
class SuperUser < User
store_accessor :settings, :privileges, :servants
end
If you are using PostgreSQL, check out HStore.
Rails as of 3.2 has key-value stores built into ActiveRecord-see here:
Where can i read more about Rails 3.2's Data Store with key-value in textfield?
in your case you can have a text field named configuration and then do this:
class Device < AR::Base
store :configuration, accessors: [:title, :background_color, ...]
...
This should work fine with forms, etc..
Two methods come to mind.
First, you could have an array of your attributes [:background_color, :title] and then iterate over them while calling define_method. You would defined two methods, define(method_name) and define("#{method_name}=").
Second, a similar idea but using method missing.
def method_missing(method_name, *args, &block)
...see if it's a get or set...
...do your stuff...
...rain dance...
...yay...
end

belongs_to association without an extra table

I have a model Download, with a table downloads. downloads has a field called ip_address, which stores an ip address as an integer. I want to set up an IpAddress model, but without a ip_addresses table, so I can do stuff like
Download.find(1).ip_address.to_s # '127.0.0.1'
Download.find(1).ip_address.to_i # 2130706433
Download.find(1).ip_address.downloads # SELECT * FROM downloads WHERE ip_address='2130706433'
IpAddress.find(2130706433).downloads # SELECT * FROM downloads WHERE ip_address='2130706433'
I want it to behave like:
class Download < ActiveRecord::Base
belongs_to :ip_address, :foreign_key => :ip_address
end
class IpAddress < ActiveRecord::Base
set_primary_key :ip_address
has_many :downloads, :foreign_key => :ip_address
end
but without having a useless table of ip addresses.
Is this possible?
EDIT
I found that ruby already has a IPAddr class.
So I did this:
require 'ipaddr'
class Download < ActiveRecord::Base
attr_accessible :ip, ...
def ip
#ip ||= IPAddr.new(read_attribute(:ip), Socket::AF_INET)
end
def ip=(addr)
#ip = IPAddr.new(addr, Socket::AF_INET)
write_attribute(:ip, #ip.to_i)
end
def self.for_ip(addr)
where(:ip => IPAddr.new(addr, Socket::AF_INET).to_i)
end
end
Then I can do lots of cool stuff
Download.new(:ip => '127.0.0.1').save
Download.for_ip('127.0.0.1').first.ip.to_i # 2130706433
belongs_to is really meant to specify an association between objects in two tables. But you're right, unless you need to store other associated data, storing IP addresses in a table is fairly useless.
However, you can use scopes to accomplish what you will want. You could have something like this in your Download model:
class Download < ActiveRecord::Base
scope :for_ip, lambda { |x| where(:ip_address => x) }
end
Then you would call
Download.for_ip(2130706433)
To get a list of downloads for that IP.
You could also add a class method instead:
class Download < ActiveRecord::Base
def self.for_ip(x)
where(:ip_address => x)
end
end
That might be handy if you want to convert from string to numeric IP addresses.
And, if you want an IPAddress class, you can add a method like this:
class IPAddress
def initialize(ip)
#presumably do some stuff here
#ip = ip
end
def downloads
Download.for_ip(#ip)
end
end
IpAddress.find(2130706433).downloads # SELECT * FROM downloads WHERE ip_address='2130706433'
This is totally a semantics issue, but this should probably change if you have no IpAddress table (i.e. how can we find the IpAddress object 2130706433 in the database if there is no IpAddress table - unless you make IpAddress a container rather than a specific single ipaddress, otherwise do something like instantiate new ones with a constructer like IpAddress(2130706433).downloads).
Otherwise, though, I don't see any problems in not having the IpAddress table. Why do you need it to be belongs_to, rather than just another column?
You can keep the models/objects if you wish to access them in similar ways:
class Download < ActiveRecord::Base
##Whatever Download-model-specific code you have...
def ip_address
#If nil, initialize new object. Return object.
#ip_address ||= IpAddress(ip_address_val)
end
end
class IpAddress
def initialize(address)
#value = address
end
def downloads
Download.where(:ip_address_val => self.value)
end
end
EDIT:
You can override the accessor, like you're asking. You just have to be careful in your code to be particular about what you're asking for.
See this doc: http://ar.rubyonrails.org/classes/ActiveRecord/Base.html
Under section "Overwriting default accessors"
Basically, if you do override the value, and if you wish to access the DB value, you use read_attribute(attr_name), so the code might look like this:
class Download < ActiveRecord::Base
##Whatever Download-model-specific code you have...
def ip_address
#If nil, initialize new object. Return object.
#ip_address ||= IpAddress(read_attribute(:ip_address))
end
end
class IpAddress
def initialize(address)
#value = address
end
def downloads
Download.where(:ip_address => self.value)
end
end
Though things might get a little confusing in your code if you aren't careful.
Add set_table_name "downloads" to your IpAddress and remove the relationship between the 2 coz it already has the column name ip_address.
This will give you queries in following way
Download.find(1).ip_address.to_s # '127.0.0.1'
Download.find(1).ip_address.to_i # 2130706433
IpAddress.find(Download.find(1).ip_address) # SELECT * FROM downloads WHERE ip_address='2130706433'
IpAddress.find(2130706433) # SELECT * FROM downloads WHERE ip_address='2130706433'

Rails 3 strip whitespace before_validation on all forms

I'm relatively new to Rails and a bit surprised this isn't a configurable behavior...at least not one I've been able to find yet?!? I would have thought that 99% of forms would benefit from whitespace being trimmed from all string & text fields?!? Guess I'm wrong...
Regardless, I'm looking for a DRY way to strip all whitespace from form fields (of type :string & :text) in a Rails 3 app.
The Views have Helpers that are automatically referenced (included?) and available to each view...but Models don't seem to have such a thing?!? Or do they?
So currently I doing the following which first requires and then includes the whitespace_helper (aka WhitespaceHelper). but this still doesn't seem very DRY to me but it works...
ClassName.rb:
require 'whitespace_helper'
class ClassName < ActiveRecord::Base
include WhitespaceHelper
before_validation :strip_blanks
...
protected
def strip_blanks
self.attributeA.strip!
self.attributeB.strip!
...
end
lib/whitespace_helper.rb:
module WhitespaceHelper
def strip_whitespace
self.attributes.each_pair do |key, value|
self[key] = value.strip if value.respond_to?('strip')
end
end
I guess I'm looking for a single (D.R.Y.) method (class?) to put somewhere (lib/ ?) that would take a list of params (or attributes) and remove the whitespace (.strip! ?) from each attribute w/out being named specifically.
Create a before_validation helper as seen here
module Trimmer
def trimmed_fields *field_list
before_validation do |model|
field_list.each do |n|
model[n] = model[n].strip if model[n].respond_to?('strip')
end
end
end
end
require 'trimmer'
class ClassName < ActiveRecord::Base
extend Trimmer
trimmed_fields :attributeA, :attributeB
end
Use the AutoStripAttributes gem for Rails. it'll help you to easily and cleanly accomplish the task.
class User < ActiveRecord::Base
# Normal usage where " aaa bbb\t " changes to "aaa bbb"
auto_strip_attributes :nick, :comment
# Squeezes spaces inside the string: "James Bond " => "James Bond"
auto_strip_attributes :name, :squish => true
# Won't set to null even if string is blank. " " => ""
auto_strip_attributes :email, :nullify => false
end
Note I haven't tried this and it might be a crazy idea, but you could create a class like this:
MyActiveRecordBase < ActiveRecord::Base
require 'whitespace_helper'
include WhitespaceHelper
end
... and then have your models inherit from that instead of AR::Base:
MyModel < MyActiveRecordBase
# stuff
end

Rails attr_accessible does not work for :type?

Im trying set the single table inheritance model type in a form. So i have a select menu for attribute :type and the values are the names of the STI subclasses. The problem is the error log keeps printing:
WARNING: Can't mass-assign these protected attributes: type
So i added "attr_accessible :type" to the model:
class ContentItem < ActiveRecord::Base
# needed so we can set/update :type in mass
attr_accessible :position, :description, :type, :url, :youtube_id, :start_time, :end_time
validates_presence_of :position
belongs_to :chapter
has_many :user_content_items
end
Doesn't change anything, the ContentItem still has :type=nil after .update_attributes() is called in the controller. Any idea how to mass update the :type from a form?
we can override attributes_protected_by_default
class Example < ActiveRecord::Base
def self.attributes_protected_by_default
# default is ["id","type"]
["id"]
end
end
e = Example.new(:type=>"my_type")
You should use the proper constructor based on the subclass you want to create, instead of calling the superclass constructor and assigning type manually. Let ActiveRecord do this for you:
# in controller
def create
# assuming your select has a name of 'content_item_type'
params[:content_item_type].constantize.new(params[:content_item])
end
This gives you the benefits of defining different behavior in your subclasses initialize() method or callbacks. If you don't need these sorts of benefits or are planning to change the class of an object frequently, you may want to reconsider using inheritance and just stick with an attribute.
Duplex at railsforum.com found a workaround:
use a virtual attribute in the forms
and in the model instead of type
dirtectly:
def type_helper
self.type
end
def type_helper=(type)
self.type = type
end
Worked like a charm.
"type" sometimes causes troubles... I usually use "kind" instead.
See also: http://wiki.rubyonrails.org/rails/pages/ReservedWords
I followed http://coderrr.wordpress.com/2008/04/22/building-the-right-class-with-sti-in-rails/ for solving the same problem I had. I'm fairly new to Rails world so am not so sure if this approach is good or bad, but it works very well. I've copied the code below.
class GenericClass < ActiveRecord::Base
class << self
def new_with_cast(*a, &b)
if (h = a.first).is_a? Hash and (type = h[:type] || h['type']) and (klass = type.constantize) != self
raise "wtF hax!!" unless klass < self # klass should be a descendant of us
return klass.new(*a, &b)
end
new_without_cast(*a, &b)
end
alias_method_chain :new, :cast
end
class X < GenericClass; end
GenericClass.new(:type => 'X') # => #<X:0xb79e89d4 #attrs={:type=>"X"}>

Resources