Ruby on Rails model association issue - ruby-on-rails

Sorry for the vague title, but it's a little much to explain in a sentence.
I've got three models, User, Device, and DeviceMessage. Their relationships are fairly simple:
a User has_many :devices,
a Device belongs_to :user,
a Device has_many :device_messages,
and a DeviceMessage belongs_to :device.
Rails provides ways to start playing with these associations quickly, like the ability to get all device messages that belong to a certain user (from any device).
In order to do this, I defined a method in the User model:
class User < ActiveRecord::Base
...
has_many :devices, :as => : owner #Other entities may "own" a device
def device_feed
DeviceMessage.that_belong_to_user(self)
end
end
And I define the called method in the DeviceMessage model:
class DeviceMessage < ActiveRecord::Base
...
belongs_to :device
def self.that_belong_to_user(user)
device_ids = "SELECT owner_id FROM devices WHERE owner_id = :user_id
AND owner_type = \"User\""
where("device_id IN (#{device_ids})", user_id: user.id)
end
end
I define a user page where they can associate a device to their account (the device has a name as well), and upon adding the device to the account, it will add the name to a list of device names in a pane to the left, while showing the user's device feed much like a twitter feed (yes, I followed Michael Hartl's RoR tutorial). At this point it is important to note that I am using helper functions to keep track of the current user so I can display this information when a user visits the root_path while logged in. When visiting the root_path, the controller for the root_path is defined so that:
if user_signed_in?
#device_feed_items = current_user.device_feed.paginate(page: params[:page])
end
And this all works perfectly!
So... what's the issue? When I create a new user via the signup page, and associate the device via the device-association page, I am redirected to the root_path, the device name is correctly displayed in the left pane (which mean the device is correctly associated with the new user), but the device_feed is not displayed.
I've used the Rails console to verify that the device messages should be showing (User.find(2).devices.first.device_messages.first displays the first message associated with the first device that is newly associated with the 2nd user), so I know that I need to reach down into the database to get a fresh rather than cached copy of the current_user, but I'm confused because it seems like that should be happening every time the user.device_feed method is called because of it's use of where() which is a part of the query API...
Any ideas? Thanks in advance for any and all answers.
-MM

I am just wondering why you have the device_feed function. For your feed display could you not just a loop like this one, this is
class Device < ActiveRecord::Base
scope :in_new_message_order, :joins => :device_messages, :order => "created_at DESC"
end
Added a joined scope
class User < ActiveRecord::Base
...
has_many :devices, :as => : owner #Other entities may "own" a device
scope :in_sort_order, order("message_date DESC")
def device_feed
DeviceMessage.that_belong_to_user(self)
end
end
Above I have added a scope to sort your messages
<% user.devices.in_new_message_order.each do |device| %>
<% device.device_messages_in_sort_order.each do |message| %>
<%= ....render out the message %>
<% end %>
<% end %>

Related

Choosing a specific row in a table in rails

I have a migration and model with a table called medications. I need to pick a specific row from the medications table. I also am trying to filter out all medications that don't have the current user's id.
Here is the current code I have.
Medication.find(:name, :conditions => { :user_id => current_user.id }, :order => "Medication.name")
I know this isn't complete, but any help would be greatly appreciated.
You can load the first medication for a specific user_id like this (assuming that your medications table has an user_id):
Medication.where(user_id: current_user.id).order(:name).first
When our User model has a belongs_to :medications it can be simplified to:
current_user.medications.order(:name).first
When you want to load the e.g. 5th medication just add an offset of 4:
current_user.medications.order(:name).offest(4).first
Or load all medications and iterate through them:
current_user.medications.limit(10).each do |medication|
puts medication.name
end
When you want to output the first ten medications on a website you would do something like this:
# in the controller
#medications = current_user.medications.order(:name).limit(10)
# in the view
<ul>
<% #medications.each do |medication| %>
<li><%= medication.name %></li>
< end %>
</ul>
The finder syntax you use is deprecated and was replaced in Rails 4. See Rails Guide about querying the database.
This is a perfect use case for a has_many :through association if you don't already have it set up.
class User < ActiveRecord::Base
has_many :prescriptions # or whatever
has_many :medications, :through => :prescriptions
end
class Prescription < ActiveRecord::Base
belongs_to :user
belongs_to :medication
end
class Medication < ActiveRecord::Base
has_many :prescriptions
has_many :users, :through => :prescriptions
end
Now you can do stuff like #user.medications to retrieve only that user's medications, #user.medications.find(params[:medication_id] to find a specific one within a user's assigned medications, and #user.medications << Medication.find_by(name: 'Aspirin') to add a medication to a user, and so on.
This is a basic overview of this technique, but it's a basic Rails concept so there's plenty of information on use cases close to whatever you may be trying to do.
I fixed the problem and I have decided to post the answer in case anybody else seems to have a similar problem.
I ended up not putting anything in my controller or adding anything new to my models. I just used this line of code in the view.
<%= Medication.offset(0).where(:user_id => current_user.id).pluck(:name).first %>
I couldn't have done it without the support of everyone who posted, Thank you!

Extract specific array in rails from API

I'm hooked up with an API, and I want to show all the profiles the user has. However I only want to show the profiles where 'service' is equal to 'test'.
Currently I'm doing:
<%= debug #client.profiles %>
which, quite obviously shows all the profiles of the client. However I want to only show the profiles where the profiles, service is equal to test.
Does anybody know how to do that? In an ideal situation I create a loop, like so:
#client.profiles.each do |profile|
and it only loops through the profiles, that I want.
You can use where clause for finding profiles where 'service' is equal to 'test'.
#client.profiles.where(service: "test")
Then you can loop with the same for profile.
You can simply do
In client model
class Client < ActiveRecord::Base
has_many :profiles, -> { where(service: "test") }
end
In Profile model
class Profile < ActiveRecord:::Base
belongs_to :client
end
In profiles table must contain client_id
In Controller
class Clients < ApplicationController
def show
#client = Client.find(params[:id])
end
end
IN/app/views/clients/show.html.erb
`
<% #client.profiles.each do |profile| %>
<%= profile.name %>
<%end %>

Finding user object in view? Ruby on Rails

<% #review.each do |review|%>
<% if review.host_id == #host.id>
<%= #user = User.find(review.user_id) %>
<% end %>
<% end %>
So I'm a bit confused. I have a few things going on here. I'm doing a loop through all reviews of hosts and then checking if the stored host.id value is equal to the active #host object's id that is passed from the controller. Problem is.. Now I need get the user object from the user ID stored in the review but, I'm unsure exactly how to do it. I can't do it from the controller as all this is done in the loop. As you can see I tried to do it with the code above but, I highly doubt I did it right. Please help me out on this. Thanks.
You should pre-load users with loading reviews, in controller. First, you should have belongs_to association, like this:
class Review < ActiveRecord::Base
belongs_to :user
# ...
end
then, in controller, you could use includes, this way:
#reviews = Review.includes(:user)
Now, for every review record in #reviews relation, to get associated user you can call user method, like this:
review.user
What's more, (and that's advantage of using includes) it doesn't fire new SQL query for every single review, so you avoid quite common N + 1 problem.
You can make a relationship in
class Review < ActiveRecord::Base
belongs_to :user
end
and then in view
review.user #gives you user
Put association in Review Model
class Review < ActiveRecord::Base
.
.
.
belongs_to :user
.
.
.
end
After putting association you can directly call association to find user object using Review object.
review.user
But this will raise N+1 query problem, so better user include user while finding review, It will execute only two queries one for finding reviews and another for finding users.
#reviews = Review.includes(:user)

Optimizing purchase querying in Rails

I am implementing an online application shop with Rails. Its data model is shown as follows:
class User < ActiveRecord::Base
has_many purchase_records
has_many items, :through => purchase_records
end
class Item < ActiveRecord::Base
has_many purchase_records
has_many users, :through => purchase_records
end
class PurchaseRecord < ActiveRecord::Base
belongs_to :user
belongs_to :item
end
It has a page showing available items and there prices, and if the user has purchased the item, the price will be a download link, just as App Store does. A view helper is written to help generate such links:
def download_link(item)
# generate a download link
end
def item_link(item)
if current_user and current_user.items.where(:id => item.id).first != nil
# User already purchased it
download_link(item, 'book-price')
else
# Not purchased yet, show price and link to its details
link_to item.price, item
end
end
current_user is defined by devise. It works fine except for it costs 20 extra database queries for a page with 20 items, since it needs to check if the user has purchased the item or not for every single item. I am wondering if it can be optimized, for example, to pre-load purchased items of current user, but I have no idea how to write it in a view helper.
I just implemented downloadable content for a client.
What I did was write an instance method on the user class that retrieves the user's purchased items, e.g.:
class User < ActiveRecord::Base
...
def downloads
self.orders.collect { |o| o.products }.flatten
end
end
You could use the include? method to check if the user purchased the item, e.g.:
def item_link(item)
if current_user && current_user.downloads.include?(item)
download_link(item, 'book-price')
else
link_to item.price, item
end
end
Unfortunately, while this is a bit more explicit, it will still loop through the user's orders every time item_link is hit. I would suggest optimizing this with Rails low-level caching where you may clear the cache every time the user logs in or completes a purchase.
A Rails low-level cache may look like this:
def downoads
Rails.cache.fetch("user-downloads-#{self.id}") do
self.orders.collect { |o| o.products }.flatten
end
end
And call the following to clear a cache:
Rails.cache.delete("user-downloads-#{self.id}")
You could set the user's purchased items to an instance variable in the controller. Then you're only hitting the database once:
# app/controllers/items_controller.rb
def index
#purchased_items = current_user.items
end
# app/helpers/items_helper.rb
def item_link(item)
if #purchased_items.include?(item)
download_link(...)
else
link_to ...
end
end
Well, you don't write that in a view helper. You make a scope on the user model called purchased_items where you would check all of the items a user has purchased.
Since you didn't put up the source code for User, Item and whatever their relationship is, I can only give you that general hint.

Trouble on saving an associated model and then retrieve some data from that

I am using Ruby on Rails 3 and I am trying to retrieve some data from a just saved child model (associated model) in order to store that data in the parent model.
More precisely (in steps) I would like to do:
Save the child model Account of the parent model User
Retrieve the just created Account ID value and save that value in the User model attribute named users_account_id.
... and more explicitly (in values) I would like to have the following scenario after saving the child model Account:
# Account values
Account.id = 222
Account.name = "Test_name"
...
Account.user_id = 111
# User values
User.id = 111
User.users_account_id = 222
I already implemented the first step, but how can I implement the second step?
In order to retrieve the Account ID, I tryed to use an association callback
class User < ActiveRecord::Base
has_one :account, :before_add => :callback_name
validates_associated :account
accepts_nested_attributes_for :account
def callback_name
self.users_account_id = Account.find_by_id(self.id).id
end
end
but I get this error:
Unknown key(s): before_add
This is way overkill. All you need to do is put the user_id in the form of the account that is getting created as a hidden field.
<% form_for(#account) do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<% end %>
Of course add your other fields that you want for account and you need a current_user object which you will need anyways with your current logic.
I'm going to side step your question a bit, and ask why you need IDs pointing in both directions? I assume you want your User to be related to an Account, and an Account to have one or more Users. The "Rails Way" to do this would be something like the following:
class User < ActiveRecord::Base
belongs_to :account
end
class Account < ActiveRecord::Base
has_many :users
end
In your database the users table will have account_id and the accounts table will not have a user_id of any kind.
This will still allow you to user the association in both directions:
some_user.account # Returns the correct account object
some_account.users # Returns all users for the account
I hope this helps somewhat!

Resources