I'm trying to figure out how I can grab the current instance of an object called "meal" to create a food object that belongs to it.
It works in console... its simple and goes like this:
user = User.first
user.meal.first.meal_foods.create
A user can have many meals, and each meal can have many meal_foods.
The challenge here for me is in the create action of the foods controller in creating my meals_food.
(I'm using STI here hence the "foods" controller and the "meal_food" name)
Currently the create action looks like this:
#food = current_user.meal.meal_foods.build
I've also tried this because something pluralizing the objects name works
#food = current_user.meal.meal_food.build
Here's the error it gives for both
undefined method `meal_foods' for [#<Meal id: 17, user_id: 1, meal_name: "Meal">]:ActiveRecord::Relation
Update:
The issue I'm having here specifically is selecting the correct meal to create a meal_food for.
In console I can select the first one, which is fine. But in the foods controller, I need to select the correct meal for which to create a meal_food.
Writing meals.first would select the very first meal for that user. If I want to select the 3rd meal out of 5, I need to figure a way to grab that meal's id.
I've just tried this:
<%= link_to "new food", foods_path(id: meal.id), method: :create %>
to pass in the meal.id as a parameter that can be used in foods_controller. And then in the foods_controller I did:
#meal_food = current_user.meals.find_by_id(params[:id]).meal_foods.build
It looks like it's submitting because the page is reloaded with a success message, but the meal_food isn't created, so it just doesn't show up.
I checked in console, and there's no new food being created for this users first meal.
Ok I realized that the link_to I wrote above creates this url:
foods?id=29
and the method I'm using to retrieve this id isn't working because params[:id] is looking for a path id, not this url id.
Thanks in advance!
The Models:
class Meal < ActiveRecord::Base
before_save :sanitize
has_and_belongs_to_many :meal_foods
attr_accessible :meal_name
def sanitize
self.meal_name = "Meal"
end
end
class Food < ActiveRecord::Base
attr_accessible :brand, :carbs, :fat, :name, :protien, :type
end
class MealFood < Food
has_and_belongs_to_many :meals
end
class User < ActiveRecord::Base
has_many :meal, dependent: :destroy
has_many :custom_foods, dependent: :destroy
The controllers:
class FoodsController < ApplicationController
#functions
def create
#this is where I need to grab the correct meal, and then create a meal_food for it...
if #meal_food.save!
flash[:success] = "Food created successfully!"
redirect_to meal_path(current_user.id)
else
flash[:error] = "Food couldn't be created."
redirect_to meal_path(current_user.id)
end
end
end
Partials:
Here's the meal partial that gets repeated to display each meal. It has the link_to for creating a meal that will belong to the meal it's under.
<tr>
<thead class=meal-thead>
<td class=meal-thead-name> <%= meal.meal_name %> </td>
<th> </th>
<th> </th>
<th> </th>
<th> </th>
<th> <%= link_to "x", meal_path(meal.id), method: :delete %> </th>
</thead>
<tbody class=meal-tbody>
<%# get reference to current meal and its foods %>
<%= render meal.meal_foods %>
<td class=remove-td-center> <%= link_to "new food", foods_path, method: :create %> </td>
</tbody>
</tr>
Couple of things:
"has_many :meal, dependent: :destroy" should be "has_many :meals, dependent: :destroy"
has_many association names are pluralized. If you want only one here, use has_one instead.
this line is wrong: #food = current_user.meal.meal_food.build, as meal is a has_many, so you need a "first". Also, it doesn't return a food, but a meal_food, so you should say this, otherwise you are confusing yourself:
#meal_food = current_user.meal.first.meal_food.build
It occurs to me that for MealFood => a better name would be Serving. Then your meal can have_many servings, and each serving can have a food. Not a mistake, just more understandable. Then you'd have the following code. Note that I didn't see the point in the STI for foods - it makes much more sense to simply put the amount of the food eaten in the servings table, then you don't need the STI at all.
class Meal < ActiveRecord::Base
before_save :sanitize
has_many :servings
has_many :foods, :through => :servings
attr_accessible :meal_name
def sanitize
self.meal_name = "Meal"
end
end
class Food < ActiveRecord::Base
attr_accessible :brand, :carbs, :fat, :name, :protein, :type
has_many :servings, :inverse_of => :food
end
class Serving < ActiveRecord::Base
attr_accessible :amount
has_many :foods
has_many :meals
end
class User < ActiveRecord::Base
has_many :meals, dependent: :destroy
end
HTH
Sometimes you say User.meal.meal_foods, sometimes you say User.meal.first.meal_foods. User.meal is an array of Meals, hence from console User.meal.first.meal_foods works, and in your code User.meal.meal_foods gives you no method error. Also, you should use plural meals in your code and swap out has_many meal with has_many meals.
Related
I've got an App in which User can buy shares in a Portfolio through a wallet.cash_transaction. Now I would like to display all shareholders of the portfolio specifying the user's name and the number of shares in a given portfolio.
#models association
class User < ApplicationRecord
has_one :wallet, as: :walletable
has_many :cash_transactions, through: :wallet
end
class Portfolio < ApplicationRecord
has_one :wallet, as: :walletable
end
class Wallet < ApplicationRecord
belongs_to :walletable, polymorphic: true
has_many :cash_transactions
end
class CashTransaction < ApplicationRecord
belongs_to :wallet
belongs_to :to_wallet, class_name: 'Wallet', optional: true
end
To display shareholders I'll need below join:
> portfolio = Portfolio.last
#shareholders = User.joins(:cash_transactions).where(cash_transactions: { to_wallet: portfolio.wallet }).uniq
Which I can use in the view by below iteration:
<tbody>
<% #shareholders.each do |user| %>
<tr>
<td><%= "#{user.first_name} #{user.last_name}" %></td>
<td><%= user.cash_transactions.where(to_wallet: portfolio.wallet).sum(:shares_number) %></td>
</tr>
<% end %>
</tbody>
Line user.cash_transactions.where(to_wallet: portfolio.wallet).sum(:shares_number) is responsible for the summation of all user shares in the portfolio. This method doesn't seem very efficient to me because it seems like I'll be sum-up unnecessarily at each iteration. I imagine that when there will be hundreds of thousands of users it can be aggravating at every refresh of the page. Is there a better way to do so?
You can write SQL queries to get sum of all matched records
#shareholders = User.joins(:cash_transactions)
.where(cash_transactions: { to_wallet: portfolio.wallet})
.select("users.*, SUM(cash_transactions.shares_number) as total_shares_number")
.group("users.id")
you will get all columns of users table and sum of total shares_number, Add more fields form another table as per your requirement
I'm trying to do something very similar to this question
I have 4 models, one of which (CoffeeBlend) is a join table:
class CoffeeRoast < ApplicationRecord
has_many :coffee_blends
has_many :coffee_beans, through: :coffee_blends
has_one :country, through: :coffee_beans
end
class CoffeeBean < ApplicationRecord
has_many :coffee_blends
has_many :coffee_roasts, through: :coffee_blends
belongs_to :country
end
class Country < ApplicationRecord
has_many :coffee_beans
end
class CoffeeBlend < ApplicationRecord
belongs_to :coffee_bean
belongs_to :coffee_roast
end
My coffee_beans table has a column called country_id which is populated with the id from the countries table.
In my coffee_roasts_show I want to be able to pull the associated country of the coffee bean. My latest attempt looks like
<% #coffee_roast.coffee_beans.country.country_name %>
which gives undefined method 'country'
Or
<% #coffee_roast.coffee_beans.countries.country_name %>
returns undefined method 'countries'
Do I have my associations correct? Is my show code wrong?
The method #coffee_roast.coffee_beans returns you association, not a single record. That's why you cannot call #country on that. If you need all the countries, you can use #map:
<% #coffee_roast.coffee_beans.map {|cb| cb.country.country_name } %>
Edit:
If you want to show the list in the browser, add = to your ERB mark:
<%= #coffee_roast.coffee_beans.map {|cb| cb.country.country_name } %>
It may also be useful to explicitly convert contry names to a string with Array#join
<%= #coffee_roast.coffee_beans.map {|cb| cb.country.country_name }.join(', ') %>
The models I have:
Category:
class Category < ApplicationRecord
has_many :categorizations
has_many :providers, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Provider:
class Provider < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Categorization:
class Categorization < ApplicationRecord
belongs_to :category
belongs_to :provider
has_many :games, dependent: :destroy
accepts_nested_attributes_for :games
end
Game:
class Game < ApplicationRecord
belongs_to :categorization
end
I need to display the games, that belongs to a specific provider. I tried to do it like:
<% #provider.categorizations.joins(:games).each do |game| %>
<%= game.title %>
<% end %>
It gives me an error: NoMethodError: undefined method 'title' for #<Categorization:0x007f2cf6ee49e8>. So, it loops through the Categorization. What is the best way to loop through the joined games table? Thanks.
First, you should do the request in your controller, or even better call a scope (defined in a model) from the controller.
Do not forget that Active Record is just an ORM, a tool allowing you to manipulate SQL.
With #provider.categorizations.joins(:games) you are not asking for games. You are asking for the categorizations and you do a JOIN with the games table. This joins is usually to allow to filter by games attributes.
To do what you want you should do the following :
#games = Game.joins(:categorization).where('categorization.provider_id = ?',#provider.id)
As you can see, the join do not return categorization, it allow me to use categorization as a filter.
You should always be aware of the SQL generated by Active Record. Look at the SQL query generated in your server's traces.
I'm guessing 'title' is an attribute of games and not categorization, so you either need to return an array of games, or add a select on the end to pull the title attribute into the categorization object, like so:
<% #provider.categorizations.joins(:games).select('dba.games.title').each do |game| %>
<%= game.title %>
<% end %>
Just to add- you shouldn't really be doing this in the view file. I'd go as far as not even doing this in the controller. I tend to encapsulate this sort of logic into a service class, which is instantiated in the controller to return a set of results. The controller should only be passing the result set on, which is then presented by the view.
class Provider < ActiveRecrord::Base
# this could be a scope instead, or in a seperate class which
# the provider model delegates to- whatever floats you boat
def get_games
# you could use pluck instead, which would return an array of titles
categorizations.joins(:games).select('dba.games.title')
end
end
class ProviderController < ApplicationController
def show
provider = Provide.find(params[:id])
#games = provider.get_games
end
end
<% #games.each do |game| %>
<%= game.title %>
<% end %>
I want to create an invoice in rails. Invoice can have items and each item will have quantity, tax & price. It's a typical invoice we see everyday.
In order to create an invoice what is the best approach.
What is the common model for invoice and items?
I know Items will be a separate model. But how can we have one view for invoice, which creates both the invoice and items added to it?
What I mean is, Inside a new invoice page, there will be list of the clients, and list of the items , But here i'm not sure how to make the association when i create invoice. Is there any good example that i can follow ?
Please I'd appreciate some Help. Or even just a walk through of the steps i need to follow in order to accomplish that...
Here's my basic ERD
Quite a broad question, here's what I'd do:
#app/models/invoice.rb
class Invoice < ActiveRecord::Base
belongs_to :user
has_many :line_items
has_many :items, through: :line_items
accepts_nested_attributes_for :line_items
end
#app/models/line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :invoice
belongs_to :item
end
#app/models/item.rb
class Item < ActiveRecord::Base
belongs_to :company
has_many :line_items
has_many :invoices, through: :line_items
end
--
#app/models/user.rb
class User < ActiveRecord::Base
has_many :invoices
end
This will be the base level "invoice" association structure - your clients/users can be built on top of it.
Your routes etc can be as follows:
#config/routes.rb
resources :invoices
#app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
def new
#invoice = current_user.invoices.new
#invoice.line_items.build
end
def create
#invoice = current_user.invoices.new invoice_params
#invoice.save
end
end
Then your view will be something like this:
#app/views/invoices/new.html.erb
<%= form_for #invoice do |f| %>
<%= f.fields_for :line_items do |l| %>
<%= f.text_field :quantity %>
<%= f.collection_select :product_id, Product.all, :id, :name %>
<% end %>
<%= f.submit %>
<% end %>
This would create the corresponding #invoice, with which you'll be able to call as follows:
#user.invoices.first
Apart from this, I don't have anywhere enough specific information to help specifically
May I recommend using the payday gem? I have created invoice models in the past applications and I'll tell you what, it can get pretty tricky sometimes depending on the type of application you're building. But the reason I like using this gem besides the convenience factor is that it can also render your invoices as a customizable PDF.
It makes adding items to the invoice a breeze as well, for example from their GitHub page:
invoice = Payday::Invoice.new(:invoice_number => 12)
invoice.line_items << Payday::LineItem.new(:price => 20, :quantity => 5, :description => "Pants")
invoice.line_items << Payday::LineItem.new(:price => 10, :quantity => 3, :description => "Shirts")
invoice.line_items << Payday::LineItem.new(:price => 5, :quantity => 200, :description => "Hats")
invoice.render_pdf_to_file("/path/to_file.pdf")
I have three tables User, User_types and Purchases.
user: id etc
user_purchase_types: id, typename, user_id
purchases: id, user_id, user_purchase_type_id, note
Users can have any number of purchases and user_types. A purchase can have one user_purchase_type.
Users can log in, create types, do purchases etc - this all works fine
However I want, when listing purchases, to see the user_purchase_types.typename, rather than the id number. Simple I think, use belongs_to, they already have the right id fields, should just work. But it doesn't
I have tried a million variations of belongs_to, has_many , has_many through etc etc but cannot get the right relationship and so show the typename rather than the id.
There are the right foreign_key id fields in the tables so this should work.
When listing the purchases in the purchase controller I use #purchase = current_user.purchases.
When looping this to display in the view I think I should be able to use purchase.user_purchase_type.typename but this gives me a 'NoMethodError'.
What am I doing wrong or should I just denormalise the DB and have done with it?
EDIT Models as req
class UserPurchaseType < ActiveRecord::Base
belongs_to :user
belongs_to :purchase
attr_accessible :typename, :id
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :purchases, :dependent => :destroy
has_many :user_purchase_types, :dependent => :destroy
end
class Purchase < ActiveRecord::Base
belongs_to :user
has_many :user_purchase_types
attr_accessible :date, :user_purchase_type_id, :note
end
index.erb file
<% #purchases.each do |purchase| %> #this works
<tr>
<td><%= purchase.user_purchase_type.typename %></td> #this fails
<td><%= purchase.date %></td>
<td><%= purchase.note %></td>
</tr><p>
<% end %>
purchase controller
def index
#user_purchase_types = current_user.user_purchase_types # this works
#purchases = current_user.purchases #this works
You just need to add foreign key in your existing association.
class Purchase < ActiveRecord::Base
belongs_to :user
has_many :user_purchase_types, :foreign_key => 'user_purchase_type_id'
attr_accessible :date, :user_purchase_type_id, :note
end
there is a one to one relationship between them rgt?
yes you should be able to be able to access typename, but by using #purchase.user_type.typename. and not purchase.user_type.typename.
and would be better if you could show user models too.
and is current_user defined?
also you can try by finding out the usertype first and then access it:
#user_type = UserType.find(#purchase.user_type_id)
#user_type.typename
You should always try the relationships first on console and see if your getting it rgt
Your relationships are all messed up.
You have a one to many relationships between purchases and user purchase type.
and therefore you can not use #purchase.user_purchase_type.typename
You have to use
#purchase.user_purchase_types.each do |i|
i.typename
end
class Purchase < ActiveRecord::Base
belongs_to :user
has_many :user_purchase_types
attr_accessible :date, :user_purchase_type_id, :note
end
The Purchase -> UserPurchaseType association is a has_many relationship but you seem to be trying to use it with user_purchase_type_id which would indicate a belongs_to :user_purchase_type relationship, but your association is has_many :user_purchase_types
It sounds like you may want something like:
<% #purchases.each do |purchase| %> #this works
<tr>
<td><%= purchase.user_purchase_types.collect(&:typename).join(',') %></td>
<td><%= purchase.date %></td>
<td><%= purchase.note %></td>
</tr><p>
<% end %>
to list all your UserPurchaseType typename's, comma separated.
If you do this, make sure when you load your #purchases in the controller to also include a .includes(:user_purchase_types). This will eager load the association and avoid rails loading each UserPurchaseType as you iterate through.