Simulate a property `MyClass.MyObjet.MyProperty` to replace an old call - ruby-on-rails

I changed some references in my Rails model to stop using some behaviour (coming from CarrierWave). I don't want to break my old code. I want the references to stay the same.
My actual call looks like: Photo.picture.url, where picture was a model by itself. The Picture class is now deprecated, and replaced by a property with the same name in the Model.
I want to use the Photo.picture.url syntax to access my picture property and URL sub-attribute, defined directly in the Photo model.
Here's my code:
class Photo < ApplicationRecord
##============================================================##
## Associations
##============================================================##
belongs_to :photoable, polymorphic: true
acts_as_list scope: [:photoable_id, :photoable_type]
## This is what Im really not sure how to do it
def picture.url
"#{self.name}"
end
end
When I call the Photo.picture, I have a NoMethodError. I want to still use the same call without breaking the references.
How can I define my property in my model so my old syntax still works?

you can do something like
Picture = Struct.new(:url)
def picture
#picture ||= Picture.new(name)
end

Related

Model column that depends on other columns

I have a gamification app that has four types of points, and the sum of all these kinds is the total points for a user, I want to be able to do sum and scopes on that column, so I think I should have it as a column in the DB.
scope :points_rank, -> { order(points: :desc) }
I was using a before_save for adding all four point types and storing it in points, but now I'm using a gem that does increment to these types of points, so when it updates those values, the before_save is not called, hence not updating the points value as expected.
What is the correct ActiveRecord callback to be using instead of before_save, or what else could I be doing to keep the column updated.
Try using the after_touch callback instead.
after_touch callback is triggered whenever an object is touched.
So, whenever point type changes, it should update the points.
First of all, counter_culture seems to be a way to enhance the counter_cache functionality of rails...
Used to cache the number of belonging objects on associations. For example, a comments_count column in a Post class that has many instances of Comment will cache the number of existent comments for each post.
It might not be exactly what you want, judging from your question.
Okay I get it. You're using points in your User model to create a "cached" column which can be used for wider application functionality. Okay that's cool...
--
Your setup, then, will look something like this (you were manually setting the counter_cache column, and now the gem handles it):
#app/models/user.rb
class User < ActiveRecord::Base
counter_cache :points
end
#app/models/point.rb
class Point < ActiveRecord::Base
belongs_to :user, counter_cache: true
end
The question is then that when you update the points model, you need to be able to update the "cached" column in the users model, now without any callbacks.
What is the correct ActiveRecord callback to be using instead of before_save
I'm presuming you're calling before_save on your User model (IE adding the associated data and putting the points column?
If so, you should try using a callback on the Point model, perhaps something like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :points
end
#app/models/point.rb
class Point < ActiveRecord::Base
belongs_to :user, inverse_of: :points
after_commit :update_user
private
def update_user
if user?
user.update(points: x + y + z)
end
end
end
--
Oberservers
If you have real problems, you could look at ActiveRecord observers.
Here's an answer I wrote about it: Ruby On Rails Updating Heroku Dynamic Routes
Whether this will trigger without any callbacks is another matter, but what I can say is that it will work to give you functionality you may not have had access to otherwise:
#config/application.rb (can be placed into dev or prod files if required)
config.active_record.observers = :point_observer
#app/models/point_observer.rb
class PointObserver < ActiveRecord::Observer
def before_save(point)
#logic here
end
end
A good way to test this would be to use it (you'll have to use the rails-observers gem) with different methods. IE:
#app/models/point_observer.rb
class PointObserver < ActiveRecord::Observer
def initialize(point)
#if this fires, happy days
end
end

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

Reference an object's parent ruby rails

I have an Opportunity model that has Links as a nested resource. I wrote a callback so that whenever I add a new link, the built-in "updated_at" attribute for my model opportunity is updated to equal Time.now. However, I'm not sure how to reference the Opportunity model. I want to do something like this:
This is what I would put in my Link model, which is a nested resource of my Opportunity model:
class Link < ActiveRecord::Base
belongs_to :opportunity
after_save :update_updated_at
def update_updated_at
#opportunity.updated_at = Time.now #this line is where I am unsure of how to reference the link's Opportunity parent
end
end
Thanks!
Links in ActiveRecord are always accessed through method names. There is no instance variable called #opportunity, so that's equivalent to calling updated_at= on nil.
What you probably want is:
def update_parent
return unless (self.opportunity)
self.opportunity.updated_at = Time.now
self.opportunity.save
end
From an implementation perspective this is a little rude as the Link object is bossing around the Opportunity one. That's usually something a controller should be doing.
If a Link object #link belongs_to an Opportunity object #opp, you can find #opp if you know #link via the ActiveRecord relation opportunity. See http://guides.rubyonrails.org/association_basics.html for more details.
Given a link record, find the parent opportunity record:
#opp = #link.opportunity
So you could write self.opportunity.updated_at = Time.now

Ruby (Rails) Delegate attributes to another model's method?

-EDIT-
After reading about the Delegate method from the first answer, my question is this, is it possible to delegate two different methods to another single method.
IE: I currently have: #photo.attachment.file.url, and #photo.attachment.height, and #photo.attachment.width
I'd like to be able to access all of these via #photo.file.url, #photo.file.height, #photo.file.width.
The reason for the syntax is Attachment is a model that uses Paperclip to manage files, Paperclip is generating the .file method (the model is called Attachment, the model uses Paperclip's has_attached_file :file).
-ORIGINAL QUESTION-
I was wondering about aliasing methods and attributes in Ruby (I think this is a general ruby question, although my application is in Rails 3):
I have two models: Photo has_one Attachment.
Attachment has "height" and "width" attributes, and a "file" method (from Paperclip).
So by default I can access bits of the Attachment model like so:
photo.attachment.width # returns width in px
photo.attachment.height # returns height in px
photo.attachment.file # returns file path
photo.attachment.file.url #returns url for the default style variant of the image
photo.attachment.file.url(:style) #returns the url for a given style variant of the image
Now, in my photo class I have created this method:
def file(*args)
attachment.file(*args)
end
So, now I can simply use:
photo.file # returns file path
photo.file.url # returns file url (or variant url if you pass a style symbol)
My question is, I was able to direct photo.attachment.file to just photo.file, but can I also map height and width to photo.file, so that, for the sake of consistency, I could access the height and width attributes through photo.file.height and photo.file.width?
Is such a thing possible, and if so what does it look like?
So what you are asking is that
photo.file --> photo.attachment.file
photo.file.url --> photo.attachment.file.url
photo.file.width --> photo.attachment.width
You can't solve this with delegates, because you want that file to mean different things based on what follows next. To achieve this you would need to reopen paperclip, which i would not recommend (because i believe the api is good the way it is).
The only way i can think of to solve this, is to add eliminate the file level too. Like so:
photo.width --> photo.attachment.width
photo.file --> photo.attachment.file
photo.url --> photo.attachment.file.url
This you could then solve by using a delegate for each of the wanted methods.
So you write
class Photo
delegate :width, :height, :file, :to => :attachment
delegate :url, :to => :'attachment.file'
end
Hope this helps.
You can use Rails 'delegate' method. Have a look at my answer for this question:
What is a more Ruby-like way of doing this command?
The simplest way that comes to mind is to delegate url method in attachment to file:
class Attachment < ActiveRecord::Base
delegate :url, :to => :file
end
This way you can call photo.attachment.url, photo.attachment.width, photo.attachment.height, which for me seems pretty consistent. You could optionally alias attachment to file - this way you'd get the exact method names you asked for (photo.file.width, photo.file.url), but I would not recommend that, because it seems confusing (calling an attachment "file").
class Photo < ActiveRecord::Base
def file
attachment
end
end
With plain Ruby you can use Forwardable:
require 'forwardable'
class RecordCollection
attr_accessor :records
extend Forwardable
def_delegator :#records, :[], :record_number
end

Ruby on Rails - Overriding the association id creation process

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?

Resources