Print attribute of object - ruby-on-rails

I have 2 classes with:
class User < ApplicationRecord
has_one :address
end
class Address < ApplicationRecord
belongs_to :user
I need to write code, which will print the value of attribute with the name 'postcode' of 100 users from database.
I have some code on this point, but not sure that it's a good way to solve the problem:
#users = User.all
#users.limit(100).each do |user|
puts "#{user.postcode}"
end
Who has better ideas?

I'd use pluck
puts User.limit(100).pluck('postcode')
# or
puts User.joins(:address).limit(100).pluck('addresses.postcode')

Pluck is best suited for your scenario.
User.where(condition).pluck(:postcode)
(#where condition is optional)
Event if you want to fetch other column with postcode you can simply include that in pluck. for e.g.
User.where(condition).pluck(:id, :postcode)
(#using multiple column inside pluck will only work with rails4 and above)

Related

Rails: Order a model by the last element of a has_many association

I have two models: User and Message that are connected by a has_many relationship. I want to get a list of users sorted by the timestamp on their last message.
class User < ActiveRecord::Base
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
When I do this:
#users = User.includes(:messages).order('messages.created_at DESC').limit(5)
It seems to order the messages, grab the newest 5 messages, and then return the users associated with those. So the number of users can be less than 5. I want to make sure I get the 5 users.
I want the query to get the newest message for each request, order the last messages, and then return the users with the newest messages. So I want something like:
#users = User.includes(:messages).order( <messages.last.created_at DESC> )
Ideally, the solution would do the sorting on the database, because I want to use paginate. If it's relevant I'm using Postgres.
I would probably be preferential to the solution mentioned by phoet of adding an attribute to User such as last_message_posted_at and using touch: true to update that on message creation. That simplifies the query that has to be performed when you need to pull your list of users. This also allows a much more readable (IMO) chain for your application:
#users = User.all.order(:last_message_posted_at)
=> "SELECT \"users\".* FROM \"users\" ORDER BY \"users\".\"last_message_posted_at\" ASC"
This also allows you to add a nice and simple scope to your User model
scope: :by_recent_message, ->{ order(:last_message_posted_at) }
User.by_recent_message.limit(5)
It also depends when and how often this #users scope is being used. Adding a few ms to the message post time is preferable, to me, than a complicated SQL query each time the list of users is pulled.
-- Edit for those who aren't familiar with the touch syntax --
Documentation for touch: http://apidock.com/rails/v4.2.1/ActiveRecord/Persistence/touch
class User < ActiveRecord::Base
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user, touch: true
end
And then make my query (to the user with most recent last message):
#user = User.includes(:messages).order(updated_at: :desc )
You can try something along the lines of
Message.group(:user_id).joins(:users).order('max(messages.created_at) desc')
you can use left join instead of includes
#users = User.joins("LEFT JOIN messages on messages.user_id = users.id").order('messages.created_at').limit(5)

rails-4 not getting all columns from 2 tables with join

This is my model.rb
model.rb
class Compute
belongs_to :user
end
class User
has_many :computes
end
I have used this query to get all details
User.joins(:computes).where.not(skey: 'NULL')
and i got all from USER table,also i need to get one or more column
from COMPUTE with USER.
You can use like this
User.joins(:computes).where.not(skey: 'NULL').select("users.id, computes.name")
Instead of .select use .pluck. Like
Table_1.joins(:table_2).where(:conditions).pluck('table_1.col_1', 'table_1.col_2', 'table_2.col_1', 'table_2.col_2')
User.joins(:computes).where.not(skey: 'NULL').pluck('users.id', 'computes.name')

Any better way to execute something like this?

I'm trying to list all the user's products with a probable association where a flag 'notification' is set to zero.
user.probable_associations.where(:notified => 0).collect{|a| Product.where(:id => a.product_id).collect{|p| p.name}}.to_sentence
It seems like using a where and collect method twice within the statement isn't very good. Is there a better way to go about this?
Also, the result is something like
"[\"Product A\"] and [\"Product B\"]"
which is pretty ugly...and I still need to remove the extra punctuation "[\" \"]
instead of something clean like
"Product A and Product B"
EDIT based on Rich's Answer, still have issues because notified is a field in associations NOT product:
has_many :probable_associations, -> { where "associations.category = 3"}, class_name: 'Association', before_add: :set_probable_category
has_many :probable_products, class_name: 'Product', through: :probable_associations, source: :product do
def not_notified
select(:name).where(notified: 0)
end
end
I'd use an ActiveRecord Association extension:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :products do
def not_notified
select(:name).where(notified: 0)
end
end
end
#-> #user.products.not_notified
That's my contribution, but you could then use #spickermann & #tompave's controbutions and use .flatten.to_sentence
Without knowing what probable_associations does would I rewrite the code to something like this:
product_ids = user.probable_associations.where(:notified => 0).map(&:product_id)
Product.where(:id => product_ids).map(&:name).to_sentence
Assuming that probable_associations is just an ActiveRecord has_many association, and that you want to end up with a list of titles for Product records, you can use this:
ids = user.probable_associations
.where(notified: 0)
.pluck(:product_id)
result = Product.where(id: ids).pluck(:name).to_sentence
It's similar to #spikermann's answer, but pluck(:column_name) is faster than using a block and only extracts the required column from the DB.
Also, the reason your code produces that string is that, by the time you call to_sentence, you have an Array of sub-arrays. Each sub-array contains a single element: a product name.
That's because the second collect is sent to an ActiveRecord::Relation containing just one record.
You could have solved that problem with flatten, but the whole operation could just be refactored.

Scoping a class method to current_user

I'm working on implementing a tagging system and I'm having problem querying for tagged objects with a scope.
For example, I would like to find all the user's items with a certain tag. With a class method I can currently find all the objects:
def self.tagged_with(name)
Tag.find_by_name(name).items
end
However, this has a problem. If I were to do something like: current_user.items.tagged_with(name) won't this existing method return ALL the items and not just items owned by the current_user? I suppose this is a simply querying issue but I can't figure out how to change a class method into something called on a collection. I have tried going the opposite way, to get a the collection through the tags, something like... tag.items.where(:user_id => current_user.id) but in this case, it's a many-to-many relationship and I haven't been able to get on thumb on this either.
What's the proper way to restrict a query like this?
Create an association on your User class that points to your Tag class.
class User < ActiveRecord::Base
has_many :tags
end
Then you can do:
current_user.tags.where(...)
If you don't already have an association in place, you'll need to create a migration to have the tags table reference your users table with a foreign key.
I think this will help you:
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by_first_name_and_last_name(first_name, last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
So, basically you can define your method tagged_with directly into the association!
This example is took from the documentations ActiveRecord::Associations

Avoid scope hitting database if association already loaded

I have 2 models like so:
class Country < ActiveRecord::Base
has_many :cities
end
class City < ActiveRecord::Base
belongs_to :country
scope :big, where("population > 1000000")
end
Then, in the code I load a country with it's cities, like so:
country = Country.include(:cities).find(id)
But when I execute:
country.cities.big
It makes a hit to the db with this query:
SELECT * FROM cities where country_id = 1 AND population > 1000000
Which works fine, but it's not necessary since the cities where all already loaded by the :include.
Is there a way to tell the scope to not hit the db if the association is already loaded?
I can do it with an association extension, but not for a regular scope. On extensions I do something like:
has_many :cities do
def big
if loaded?
detect {|city| city.population > 1000000}
else
where("population > 1000000")
end
end
end
But this would be repeating the scope in 2 places and I want to reuse the scope on the city model.
The scope logic uses methods that work with Arel under the hood, and ruby Enumerables don't know how to use them. You may be able to refactor your logic to an abstraction that can be translated to use either the Arel or Enumerable methods, but this won't always be possible:
def self.build_scope(abstracted)
where(abstracted.map(&:to_s).join(' '))
end
def self.build_enum(abstracted)
select{|city| city.send(abstracted[0]).send(*abstracted[1..2]) }
end
def self.abstract_big
[:population, ">", 10000]
end
scope :big_scope, build_scope(abstract_big)
def self.big_enum
build_enum abstract_big
end
You could then do:
country.cities.big_enum
A much better idea would be to only eagerly load according to the scope that you want (if you know it in advance):
country = Country.include(:cities).merge(City.big).find(id)

Resources