Ruby on Rails - Overriding the association id creation process - ruby-on-rails

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?

Related

how to run a one-time database change on a single user

I have Customer and each customer has_many Properties. Customers belong to a Company.
I'm trying to add a certain Property to each one of a single Company's Customers. I only want this change to happen once.
I'm thinking about using a migration but it doesn't seem right to create a migration for a change that I only ever want to happen once, and only on one of my users.
Is there a right way to do this?
You can just use rails console.
In rails c:
Company.where(conditions).last.customers.each do |customer|
customer.properties << Property.where(condition)
customer.save!
end
Validation
Depending on how you're changing the Customer model, I'd include a simple vaidation on the before_update callback to see if the attribute is populated or not:
#app/models/Customer.rb
class Customer < ActiveRecord::Base
before_update :is_valid?
private
def is_valid?
return if self.attribute.present?
end
end
This will basically check if the model has the attribute populated. If it does, it means you'll then be able to update it, else it will break
--
Strong_Params
An alternative will be to set the strong_params so that the attribute you want to remain constant will not be changed when you update / create the element:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
...
private
def strong_params
params.require(:model).permit(:only, :attributes, :to, :update)
end
end
It would be much more helpful if you explained the context as to why you need this type of functionality - that will give people the ability to create a real solution, instead of proposing ideas

Adding a new column to acts_as_taggable_on tag

Basically I would like to add the ability to vote on tags, so I would like to have a priority column for each different model's tag.
Any ideas about how to do this?
I suspect I need to make a new migration, but I don't know what to make it for. What would the migration be?
Thanks!
As I remember, acts_as_taggable creates a table called tags, so you add a field to that table:
rails g migration add_votes_to_tag votes:integer
and add your logic to vote on tag.
P.S. Not sure if I understood correctly your question.
If you want to extend the regular usage of the tag class, seems to be the case, and create a special case for those tags that needs to be counted you can rely on a hook method from the core named [find_or_create_tags_from_list_with_context][1]
class Company < ActiveRecord::Base
acts_as_taggable_on :markets, :locations
def find_or_create_tags_from_list_with_context(tag_list, context)
if context.to_sym == :markets
MarketTag.find_or_create_all_with_like_by_name(tag_list)
else
super
end
end
end

Non persistent ActiveRecord model attributes

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

Active Record Association with Memcached

Hi for rails model association, i know i can do this:
For example a model class Page.
class Page < ActiveRecord::Base
has_many :parts
end
I can do this:
Page.first.parts.find_by_name('body')
Page.first.parts.class actually returns Array. How can it activate methods for Part model? I found the similar post on How do rails association methods work?
My question is that when i try to use memcache to cache the response for parts methods. Then when i call Page.first.parts.find_by_name('body'), it tells me that the Array doesn't have method find_by_name. How do i solve this problem? I need to have the cache as this is one heavily used methods.
class Page
def parts_with_cache
Rails.cache.fetch("parts_for_page_#{id}", {:expires_in => 1.minutes}) do
parts_without_cache
end
end
alias_method_chain :parts, :cache
end
Since you are getting back an array of Parts objects associated to the Page object unfiltered by part name, just do an Array select method on the result set.
body_parts = Page.first.parts.select{ |part| part.name == 'body' }

Conditional model updating -- Rails 3.1

I have two tables:
stores
raw_stores_data
The raw_stores_data is received from a third party daily.
I'd update certain fields of the stores model if those fields have been modified for that record in raw_stores_data.
Currently I have a bunch of conditional statements that check each of those fields. Is there any better way to code this?
new_data = raw_stores_data.all.select do |item|
item.store_id.present?
end
new_data.each do |item|
if item.field1 != item.stores.field1
...
...
...
# update record with hash of fields to update created above
end
You could add an association and special mutators to the 'raw' model that know how manipulate the 'stores' object. This serves to keep the model code in the model. Thin controller, comprehensive models, etc.
class Store < ActiveRecord::Base
has_one :raw_stores_data
end
class RawStoresData < ActiveRecord::Base
belongs_to :store
def field1=(value)
store.field1 = value
store.save!
field1 = value
end
end
I'm hand waving at some of the details, and you might want to reverse the direction of the association or make it go both directions.
EDIT:
You would use this as such:
raw_data = RawStoreData.find(param[:id]) # or new or however you get this object
raw_data.field1 = param[:field1]
The act of assigning will use the 'field1=' method, and make the change to the associated store object. If you're worried about saving unnecessarily, you could conditionalize in that method to only save if the value changed.
I hope this is clearer.

Resources