Is it possible in ActiveRecord to customize/override the name of an attribute so that it does not match the column name in the database?
My specific case involves a legacy column, "revision", that I can't remove at this time. The column name conflicts with acts_as_audited. Which of course errors out the legacy code that I need until my migrations are complete.
My desired solution would be to override the attribute name for this column, and update the few areas that call it. Thus allowing the legacy column to live alongside acts_as_audited.
I haven't used acts_as_audited, but I'm assuming its implementation is overriding the accessor for that column. In that case, you should be able to just do something like this:
class ActiveRecord::Base
def self.name_column(column_name, new_name)
define_method(new_name) {read_attribute column_name}
define_method("#{new_name}=") {|value| write_attribute column_name, value}
define_method("#{new_name}?") {attribute_present? column_name}
end
end
These will directly access the column named in column_name without going through the overridden accessor.
Oh, bonus duplication-destroying metaprogramming answer:
class ActiveRecord::Base
def self.name_column(column_name, new_name)
{ '' => :read_attribute,
'=' => :write_attribute,
'?' => :attribute_present? }.each do |suffix,method|
define_method("#{new_name}#{suffix}") {|*args| send method, column_name, *args}
end
end
end
Just because I like to show how it can be done.
Create a migration to rename the column from revision to whatever-you-want.
Then you can declare an attr_accessor :revision and use it without the need to map the attribute to a database field.
Related
I have a model named end. It works fine in my development environment where I use SQLite.
But in production I get an error because of PostgreSQL where end is a reserved word.
I don't want to rename the field in the model, because there are too many files to edit.
Instead, I want to declare a mapping rule so that field name in model stay "end" but name of this field in database became end_date.
How I can do it?
Your best bet, long-term, is almost certainly to suck it up and change all your Ruby code to use end_date. Obviously, that's going to be tedious because end is a Ruby keyword too, meaning Search & Replace won't Just Work; so if you really can't face it try this.
Change the name in the database, then add the following two methods to your model:
class Widget < ActiveRecord::Base
def end
end_date
end
def end=(val)
self.end_date = val
end
end
I'd like to use an if statement to check if the value in a Postgres DB table is unique. If unique, then do something, if not unique, do something else. Here's what the pseudo code would look like in Ruby on Rails.
if validates_uniqueness_of :number == "true"
puts "this value is unique and should be added to the DB"
else
puts "this value is not unique and should not be added to the DB"
end
Can this type of logic be implemented in the model or controller? If yes, which is the better way to go? If no, what should I do instead? Also, what would the syntax look for something like this?
Thanks guys!
You can use the exists? method to check whether a record is in the database already. It can take a hash of fields you want to search on:
before_create :do_something_if_unique
def do_something_if_unique
if self.class.exists?(number: number)
# there is a record that exists with this number
else
# there are no records that exist with this number
end
end
In order to found unique values in a column, I'd do something like:
def self.has_unique_numbers?
pluck(:number).uniq.count == 1
end
Then in your model or controller, you can ask:
if YourModel.has_unique_numbers?
# Some Code
else
# Some other code
end
You can use first_or_create:
MyModel.where(number: number).first_or_create!
If nothing is found matching the given criteria (more AR methods can be chained in as well), the object is saved to the database. Whether it should go in the model or controller depends on how you are using it. It should work fine in either.
Take a look at :validates_uniqueness_of for model validations. Whether or not you want to additionally add database constraints is a bigger issue open to a lot of debate depending on what side of the fence you sit on :)
class MyThing < ActiveRecord::Base
validates :number, :uniqueness => true
end
a = MyThing.create(:number => 1) # will succeeed
b = MyThing.create(:number => 2) # will fail and a.errors will contain more info.
I want to add to an existing model some attributes that need not be persisted, or even mapped to a database column.
Is there a solution to specify such thing ?
Of course use good old ruby's attr_accessor. In your model:
attr_accessor :foo, :bar
You'll be able to do:
object.foo = 'baz'
object.foo #=> 'baz'
I was having the same problem but I needed to bootstrap the model, so the attribute had to persist after to_json was called. You need to do one extra thing for this.
As stated by apneadiving, the easiest way to start is to go to your model and add:
attr_accessor :foo
Then you can assign the attributes you want. But to make the attribute stick you need to change the attributes method. In your model file add this method:
def attributes
super.merge('foo' => self.foo)
end
In case anyone is wondering how to render this to the view, use the method arguments for the render method, like so:
render json: {results: results}, methods: [:my_attribute]
Please know that this only works if you set the attr_accessor on your model and set the attribute in the controller action, as the selected answer explained.
From Rails 5.0 onwards you could use attribute:
class StoreListing < ActiveRecord::Base
attribute :non_persisted
attribute :non_persisted_complex, :integer, default: -1
end
With attribute the attribute will be created just like the ones being persisted, i.e. you can define the type and other options, use it with the create method, etc.
If your DB table contains a matching column it will be persisted because attribute is also used to affect conversion to/from SQL for existing columns.
see: https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute
In my case I wanted to use a left join to populate custom attribute. It works if I don't add anything but I also want to be able to set the attribute on a new object and of course it doesn't exist. If I add attr_accessor then it always returns nil after a select. Here's the approach I've ended up with that works for setting on new object and retrieving from left join.
after_initialize do
self.foo = nil unless #attributes.key?("foo")
end
def foo
#attributes["foo"]
end
def foo=(value)
#attributes["foo"] = value
end
I have a legacy table with a column for the last update timestamp.
Now I do want to tell my model that the rails attribute updated_at is mapped to the legacy column.
alias_attribute :updated_at, :lastcall
Now I can access the column but it's not getting updated when i update the object.
So how can I use the rails timestamps with an legacy column?
Best,
P
Try to add this as well, which will alias the setter method.
alias_attribute :updated_at=, :lastcall=
I don't know if there's a 'proper' way of doing it, but you could do it with a before_save or before_update filter on the model.
class LegacyModel < ActiveRecord::Base
before_update :update_lastcall
private
def update_lastcall
self.lastcall = Time.now
end
end
If you don't want to get the model messy you could put it into an Observer.
I'd also like to draw your attention to this, if your timestamp column names are site-wide (as mine are). I didn't want to clutter up my models, and fortunately, you can monkey-patch ActiveRecord::Timestamp. I placed the below into a dir named lib/rails_ext/active_record.rb (I'm an organization freak) and called it with a require 'rails_ext/active_record' declaration in one of my initializers in config/initializers/.
module ActiveRecord
module Timestamp
private
def timestamp_attributes_for_update #:nodoc:
[:modified_time, :updated_at, :updated_on, :modified_at]
end
def timestamp_attributes_for_create #:nodoc:
[:created_date, :created_at, :created_on]
end
end
end
My custom attributes ended up being :modified_time and :created_date. You'd specify your :lastcall column in one of those (timestamp_attributes_for_update, I'm assuming). No mucking with your models required.
I'm trying to override the way rails apply and id to an associated object, for example:
There are 2 simple models:
class Album < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :album
end
And then I want to do this:
album = Album.new :title => 'First Album'
album.photos.build
album.save #=> true
On this case I've created a plugin that overrides the id property and replaces it to a hashed string, so what I want to do is find the methods where this album_id is being replaced for my custom method instead of the int and be able to converted before it's saved.
But I want to act globally inside Rails structure because since it will be a sort of plugin I want to make this action work on dynamic models, that's why I can't create an before_save validation on the model.
I'm not sure if it's easy to understand, but I hope someone could help me on that..
Here's a screenshot of my current table so you can see what is happening:
SQLite3 DB http://cl.ly/1j3U/content
So as you can see the album_id it's being replaced for my custom ruby object when its saved...I've disabled the plugin and then it saved normally with records 11 and 12...
I want just act on a rails action and converted with my custom methods, something like
def rails_association_replaced_method(record)
#take the record associations and apply a to_i custom method before save
super(record)
end
something like this :)
Well I hope this didn't get too complicated
Cheers
It seems if I only override theActiveRecord::Base save method do the job if handled properly
define_method 'save' do
int_fields = self.class.columns.find_all { |column| column.type == :integer }
int_fields.each do |field|
if self.attributes[field.name]
self.attributes[field.name] = self.attributes[field.name].to_i
end
end
super
end
And this shall replace all the integer fields from the Current Model applying a to_i method over the result.
Rails is unfriendly to that kind of change to the defaults. What's your end goal here?