Interaction of model methods rails - ruby-on-rails

Inside my "Course" model:
One method is supposed to select all user_ids which belong to a certain course
def participants
Course.joins(:click).pluck(:user_id)
end
The other method is supposed to pick a random user_id
def set_winner
Course.participants.sample
end
However, I get the following error:
undefined method `participants' for #<Class:0x007fc639811468>
If somebody could explain to me, why this doesn't work, I'd be really grateful.

Your example doesn't work because you define instance methods. And then you try to run them on class as if they are class methods.To fix it you could write:
def self.participants
Course.joins(:click).pluck(:user_id)
end
def self.set_winner
Course.participants.sample
end
or, better
class Course < ActiveRecord::Base
scope :participants, -> { joins(:click).pluck(:user_id) }
scope :set_winner, -> { participants.sample }
end

Related

Pulling attribute names from associated child model to parent model

class User
has_many :addresses
def csv_header
self.addresses.attribute_names
end
def csv_values
self.addresses.all do |addr|
addr.attributes.values
end
end
end
class Address
belongs_to :user
end
*i am trying to pull the attribute names of the address model to user model,but this method isn't working so can anyone help *
Not much needed here - I think you just need to map the addresses in csv_values.
class User
has_many :addresses
def csv_header
addresses.attribute_names
end
def csv_values
addresses.map do |addr|
addr.attributes.values
end
end
end
class Address
belongs_to :user
end
Does that fix this for you?
I'd be tempted to shift things around a little for clarity in the code and make use of delegate:
class user
...
delegate :attribute_names, to: :addresses, prefix: true, allow_nil: true
...
end
class Address
...
def self.mapped_values
all.map { |addr| addr.attributes.values }
end
...
end
Then you can just call user.addresses_attribute_names and user.addresses.mapped_values.
You can also just call Address.column_names to get the header array, if it will always stay consistent, as is likely to be the case.
Hope that helps!
Update based on comment:
To achieve the same for users, you can call the following:
Either call User.column_names or user.attribute_names to get the headers (on the class for the former, and an instance for the latter).
If you also need the users' mapped values, you can copy across the self.mapped_values method from the address model and use that. It's a little duplication, but for a pair of methods like this I wouldn't be inclined to separate these into a separate module.
Final tip - if you're calling the address methods from a collection of users (i.e. User.all) make sure you adjust it to include the addresses to avoid hitting your database in an inefficient way (User.includes(:addresses)...).

What is the standard way to call a method on an array of instances?

I am trying to figure out what the best way is to write a method for an array of instance of a class in ruby on rails.
I have the following solution:
class Asset < ActiveRecord::Base
scope :owned_by, -> (user) { where(:user_id => user.id) }
def self.get_tokens(user)
all.owned_by(user).pluck(:tokens)
end
end
I am not totally satisfied with the solutions since the method is called on the class
Asset.get_tokens(user)
I would rather call it on an array of asset instances without having to do a map in the controller. Something like this:
user_assets.get_tokens()
Is there a standard way to do this?
Firstly, you don't need class method, you can call scope directly,
class Asset < ActiveRecord::Base
scope :owned_by, -> (user) { where(:user_id => user.id) }
end
to use, just say Asset.owned_by(user)
To get the records in inverse form(from user), assuming, User has_many Asset
class User < ActiveRecord::Base
def tokens
assets.pluck(:tokens)
end
end
Which can be called as, User.find(1).tokens
If you want to return an array of items from an array, instead of doing this...
array = []
some_collection.each do |item|
array << item.value
end
you can just do this:
array = some_collection.map(&:value) # same as some_collection.map { |item| item.value }
which is the same as
array = some_collection.pluck(:value)

Rails includes cache not being used in model method

So in a rails-api I'm working on, we're currently trying to optimize some of the longer running calls, and I'm having an issue with the .includes functionality. I've got it working in most situations, but there's one particular situation where it's not working in the way that I want it to.
Here's an example:
User class
class User < ActiveRecord::Base
has_many :images
has_one :active_image, -> { where(images: { active_image: true })}, class_name: 'Image'
has_many :facebook_auth
def get_profile_image
if active_image
active_image.image.url(:profile)
else
facebook = facebook_auth.last
if facebook
"https://graph.facebook.com/#{facebook.provider_user_id}/picture?width=150&height=150"
end
end
nil
end
end
Controller:
class UserController < BaseAPIController
def get_user_image
user_id = params[:user_id]
user = User.includes(:active_image, :facebook_auth).find(user_id)
render json: user.get_profile_image
end
end
With this, I would assume that the .includes(:active_image, :facebook_auth) would cache the data so that when I call them in the get_profile_image method, it doesn't make any more db calls, but this isn't the case. What am I doing wrong here?
Thanks,
Charlie
You where almost there!
Try this approach:
class User < ApplicationRecord
has_many :images, dependent: :destroy
has_one :active_image,
-> { where(active: true) },
class_name: 'Image'
has_many :facebook_auths, dependent: :destroy
has_one :active_facebook_auth,
-> { order("created_at desc") },
class_name: 'FacebookAuth'
scope :eager_load_image_data,
-> { includes(:active_image).includes(:active_facebook_auth) }
def profile_image_url
if active_image
active_image.url
elsif active_facebook_auth
"https://graph.facebook.com/#{active_facebook_auth.provider_user_id}/picture?width=150&height=150"
else
nil
end
end
end
Then in your controller or whenever you want to eager load images:
# for one user, with id 2:
User.eager_load_image_data.find(2).profile_image_url
# for a collection (using 'all' here):
User.eager_load_image_data.all.map{ |user|
[user.name, user.profile_image_url]
}
This way the image data is eagerloaded, both from the Image class and the FacebookAuth class.
There where also some other issues in your method User#get_profile_image that I have fixed:
It always returns nil. I am sure in your real code you have early returns.
For collections, it does a N+1 query if looking for facebook_auth_tokens.
Well, I wanted to comment, but couldn't put code into the comments, so I'm giving a non-answer...
I don't see anything obviously wrong, but as a work around, you could do this in User or somewhere:
def self.user_profile_image(user_id)
active_image = Images.where(user_id: user_id).where(active_image: true).first
if active_image
active_image.image.url(:profile)
else
facebook = FaceBookAuth.where(user_id: user_id).last
if facebook
"https://graph.facebook.com/#{facebook.provider_user_id}/picture?width=150&height=150"
end
end
nil
end
And just call/cache the image in your controller, if that's not overly simplistic...
def get_user_image
render json: User.user_profile_image(params[:user_id])
end
That makes at most 2 relatively efficient queries. It doesn't needlessly load user, etc.

how to call a class, after_create method from either a spec or rails console

I have the following
class Item < ActiveRecord::Base
after_create TellMe
end
class TellMe
def self.after_create(model)
end
and would like to be able to do something analogous to this:
i=Item.new :name => 'my test name'
i.send(:TellMe.after_create)
similar to how I could call send on a public method? It looks like I can do
i.instance_eval 'TellMe.after_create(self)'
but feels a little ugly (amongst other things)
The only way to trigger a callback is to do a qualifying event, in this case, creating an item.
As a workaround to what you want, you could just create another method that will do exactly what the callback would do and you would be able to access it like normal
Class Item < ActiveRecord::Base
def tellme(params)
TellMe.function(params)
end
end

Silly rails question: undefined method within class declaration

I have a user class, where I am trying to attach a profile created by a factory. Here is the class:
class User < ActiveRecord::Base
acts_as_authentic
has_one :profile
after_create {self.profile = ProfileFactory.create_profile(self.role)}
end
and the factory looks like this
class ProfileFactory
def self.create_profile(role)
String s = "#{role}#{"Profile"}"
Object.const_get(s).new
end
end
For some reason it doesnt recognize self as a User. This is the error I get on making the ProfileFactory.create_profile call
undefined method 'role' for
#<Class:0x2304218>
The user object has a role: String declared in its migration.
Any help is appreciated thanks.
Duncan's got the correct answer in terms of using your factory as a callback. But it might help you to understand what was going wrong.
Class methods receive the class as self, instance methods receive the instance as self. When you provide any method a block, the scope of the calling method is used for the block.
after_create is a class method that adds a call back to the block provided or methods listed as arguments. Blocks provided to callbacks (after_create, before_save, etc) are interpreted in the context of class methods. So self refers not, to the object being created, but the Class of the object being created.
In this snippet:
after_create {self.profile = ProfileFactory.create_profile(self.role)}
self is the User class, not an instance of the User class as you expect.
Compared to the more traditional after_create syntax that Matt was hinting at, where an instance method is added to the callback chain. In this context self refers to the instance.
class User < ActiveRecord::Base
has_one :profile
after_create :add_profile
protected
def add_profile
self.profile = ProfileFactory.create_profile(role)
end
end
EmFi, This makes a lot of sense. So
just to clarify, when invoking methods
that are in the class from the
callback methods but NOT actually in
one of the callback methods, allows us
to get around this class method
problem, and use the current instance?
Yes, but not for the reasons you think. Callbacks only looks for instance methods when passed a symbol.
Instead what you've found a way to get around the instance method problem. You can't give a callback a class method, but you can provide it a block in which it calls one. I guess you could also define an instance method that calls the class method, but that seems a little backwards.
The User object in question is passed to the after_create block as a parameter.
class User < ActiveRecord::Base
after_create do |user|
user.profile = ProfileFactory.create_profile(user.role)
user.save
end
end
Why not do something more simplistic? Something like:
class User < ActiveRecord::Base
has_one :profile
after_create :add_profile
protected
def add_profile
self.create_profile(:role => self.role)
end
end
class Profile < ActiveRecord::Base
belongs_to :user
end
Have you come from a Java background perchance?

Resources