I have checkedout the docs on this but I am still a bit confused. My goal is to return the content field on #mom. But it fails with undefined method `content'. and #goals works. What am I missing about #mom and how can I get that to work?
project_controller.rb
def show
#project = Project.find(params[:id])
#goals = #project.projectgoals.find(:first, :order => "created_at DESC")
#mom = #project.projectgoals.order(:created_at => "DESC").limit(1).all
end
Show.html.erb
<b>Name: </b><%= #project.name %><br/>
<b>Goals: </b><%= #goals.content %><br/>
<b>Goals: </b><%= #mom.content %>
<br/>
<%= debug #mom %>
Models
class Projectgoal < ActiveRecord::Base
attr_accessible :content, :project_id
belongs_to :projects
end
class Project < ActiveRecord::Base
attr_accessible :name
has_many :projectgoals
has_many :projectstatuses
end
Try this in your controller instead (it'll return one record rather than an array with one record):
#mom = #project.projectgoals.order("created_at DESC").first
Related
So I am working on a recipe book rails application. A user can create a recipe, view the recipe, and update the recipe. However, when I update a particular ingredient's quantity (i.e. Pasta "2 Cups" ) it changes all the other recipes that contain pasta to that new quantity ("2 Cups"). I can see in my rails console and server that it recognizes the change and updates, but when I display the view it looks as if it displays the first instance of the ingredient and not the one that I just updated. I have a strong feeling that it is an error with my quantity method in ingredients, but i'm not sure how to fix it.
Recipe Model
class Recipe < ApplicationRecord
belongs_to :user, required: false
has_many :recipe_ingredients
has_many :ingredients, through: :recipe_ingredients
validates :name, presence: true
validates :instructions, presence: true
validates :cooktime, presence: true
def self.alphabetize
self.order(name: :asc)
end
def ingredients_attributes=(ingredients_attributes)
self.ingredients = []
ingredients_attributes.values.each do |ingredients_attribute|
if !ingredients_attribute[:name].empty?
new_ingredient = Ingredient.find_or_create_by(name:
ingredients_attribute[:name])
self.recipe_ingredients.build(ingredient_id: new_ingredient.id,
quantity: ingredients_attribute[:quantity])
end
end
end
end
Ingredients Model
class Ingredient < ApplicationRecord
has_many :recipe_ingredients
has_many :recipes, through: :recipe_ingredients
def self.alphabetize
self.order(name: :asc)
end
def quantity
recipe_ingredient = RecipeIngredient.find_by(recipe_id:
self.recipes.first.id, ingredient_id: self.id)
recipe_ingredient.quantity
end
end
Recipe Show, Edit and Update Actions:
def show
#recipe = Recipe.find(params[:id])
#ingredients = #recipe.ingredients.alphabetize
end
def edit
#recipe = Recipe.find(params[:id])
end
def update
#recipe = Recipe.find(params[:id])
if #recipe.user = current_user
if #recipe.update(recipe_params)
redirect_to #recipe
else
render :edit
end
end
end
views/recipes/show (ingredients - quantity) listing:
<% #recipe.ingredients.each do |ingredient|%>
<li><%=ingredient.name %> - <%=ingredient.quantity%></li>
<%end%>
Just to riff on abax's answer a bit. I think I would do something like:
class Ingredient < ApplicationRecord
has_many :recipe_ingredients
has_many :recipes, through: :recipe_ingredients
def self.alphabetize
self.order(name: :asc)
end
def quantity_for(recipe)
recipe_ingredients.where(recipe: recipe).first.quantity
end
end
Which you would use something like:
<% #recipe.ingredients.each do |ingredient|%>
<li>
<%= ingredient.name %> - <%= ingredient.quantity_for(#recipe) %>
</li>
<%end%>
IMO, ingredient.quantity_for(#recipe) indicates very clearly what's going on: the ingredient is returning the quantity_for a particular #recipe.
BTW, this:
def quantity
recipe_ingredient = RecipeIngredient.find_by(
recipe_id: self.recipes.first.id,
ingredient_id: self.id
)
recipe_ingredient.quantity
end
Is all sorts of wrong. In addition to the problem identified by abax, you don't need to do
RecipeIngredient.find_by(ingredient_id: self.id)
That's why you did has_many recipe_ingredients! So that you can simply do:
recipe_ingredients.where(recipe: recipe)
And, note that when you do associations, you don't have to say recipe_id: recipe.id. Rails allows you to simply do recipe: recipe. Less typing. Fewer typos.
Finally, this assignment:
recipe_ingredient = RecipeIngredient.find_by(...)
Isn't necessary. You could just do:
RecipeIngredient.find_by(
recipe: recipe,
ingredient: self
).quantity
Which, of course, you would never do. Because, as above, it's much nicer to do:
recipe_ingredients.where(recipe: recipe).first.quantity
It looks like this is the problem area:
class Ingredient < ApplicationRecord
def quantity
recipe_ingredient = RecipeIngredient.find_by(recipe_id:
self.recipes.first.id, ingredient_id: self.id)
recipe_ingredient.quantity
end
<%=ingredient.quantity%> will return the quantity of the first recipe to use the ingredient no matter the recipe. I think you should be accessing the recipe_ingerdient.quantity instead.
<% #recipe.recipe_ingredients.each do |recipe_ingredient|%>
<li><%=recipe_ingredient.name %> - <%=recipe_ingredient.quantity%></li>
<%end%>
as it looks like the quantity for each recipe is saved in the recipe_ingredient join table.
Edit: This approach would require adding delegate :name, to: :ingredient to class RecipeIngredient
I would start with a more modest goal to start with - just to let users create nested RecipeIngedients:
<%= form_for(#recipe) do |f| %>
<%= fields_for(:recipe_ingredients) do |ri| %>
<%= ri.collection_select(:ingredient_id, Ingredient.all, :id, :name) %>
<%= ri.text_field(:quantity) %>
<% end %>
<% end %>
class Recipe < ApplicationRecord
# ...
accepts_nested_attributes_for :recipe_ingredients
end
def recipe_attributes
params.require(:recipe).permit(:foo, :bar, recipe_ingredient_attributes: [:ingredient_id, :quantity])
end
You can then expand this by using an additional level of nested attributes:
<%= form_for(#recipe) do |f| %>
<%= f.fields_for(:recipe_ingredients) do |ri| %>
<div class="recipe-ingredient">
<%= ri.collection_select(:ingredient_id, Ingredient.all, :id, :name) %>
<%= ri.fields_for(:ingredient) |ingredient| %>
<%= ingredient.text_field :name %>
<% end %>
</div>
<% end %>
<% end %>
class RecipeIngredient < ApplicationRecord
# ...
accepts_nested_attributes_for :ingredient, reject_if: [:ingredient_exists?,
private
def ingredient_exists?(attributes)
Ingredient.exists?(name: attributes[:name])
end
def ingredient_set?(attributes)
self.ingredient.nil?
end
end
def recipe_attributes
params.require(:recipe)
.permit(:foo, :bar,
recipe_ingredients_attributes: [:ingredient_id, :quantity, { ingredient_attributes: [:name] }]
)
end
But using nested attributes more than one level down usually turns into a real mess since you´re stuffing so much functionality down into a single controller. A better alternative both from the the programming and UX perspective might be to use ajax to setup an autocomplete and create the records on the fly by POST'ing to /ingredients.
I am using Rails 5.1 and im having some issues saving params on an n:n relationship.
I have three models:
class Course < ApplicationRecord
belongs_to :studio
has_many :reviews, dependent: :destroy
has_many :has_category
has_many :categories, through: :has_category
validates :name, presence: true
end
class Category < ApplicationRecord
has_many :has_category
has_many :courses, through: :has_category
end
class HasCategory < ApplicationRecord
belongs_to :category
belongs_to :course
end
and a simple form to create a new course with different categories using check_box_tag (not sure if using it correctly though)
<%= simple_form_for [#studio, #course] do |f| %>
<%= f.input :name %>
<%= f.input :description %>
<% #categories.each do |category| %>
<%= check_box_tag "course[category_ids][]", category.id, true %>
<%= category.name%>
<% end %>
<%= f.button :submit %>
<% end %>
And all is permitted and created on the courses controller:
def new
#studio = Studio.find(params[:studio_id])
#course = Course.new
#course.studio = #studio
#categories = Category.all
end
def create
#studio = Studio.find(params[:studio_id])
#course = Course.new(course_params)
#course.studio = #studio
#categories = params[:category_ids]
if #course.save
redirect_to course_path(#course)
else
render :new
end
end
def course_params
params.require(:course).permit(:studio_id, :name, :description, :category_ids)
end
With better_errors i know the categories are being requested, here the request info:
"course"=>{"name"=>"Course test", "description"=>"testing", "category_ids"=>["2", "3"]}, "commit"=>"Create Course", "controller"=>"courses", "action"=>"create", "studio_id"=>"16"}
but the categories are not saved on course_params, HasCategory instance or on the Course, i´ve tried with #course.categories = params[:category_ids] and other solutions without success.
How do i save the categories to the courses?
Try the following
Change the strong parameter with category_ids: []
def course_params
params.require(:course).permit(:studio_id, :name, :description, category_ids: [])
end
Comment out this line #categories = params[:category_ids]
Hope it helps
I have three model classes related to each other.
class Student < ActiveRecord::Base
has_many :marks
belongs_to :group
accepts_nested_attributes_for :marks,
reject_if: proc { |attributes| attributes['rate'].blank?},
allow_destroy: true
end
This class describes a student that has many marks and I want to create a Student record along with his marks.
class Mark < ActiveRecord::Base
belongs_to :student, dependent: :destroy
belongs_to :subject
end
Marks are related both to the Subject and a Student.
class Subject < ActiveRecord::Base
belongs_to :group
has_many :marks
end
When I try to create the nested fields of marks in loop labeling them with subject names and passing into in it's subject_id via a loop a problem comes up - only the last nested field of marks is saved correctly, whilst other fields are ignored. Here's my form view code:
<%= form_for([#group, #student]) do |f| %>
<%= f.text_field :student_name %>
<%=f.label 'Student`s name'%><br>
<%= f.text_field :student_surname %>
<%=f.label 'Student`s surname'%><br>
<%=f.check_box :is_payer%>
<%=f.label 'Payer'%>
<%= f.fields_for :marks, #student.marks do |ff|%>
<%#group.subjects.each do |subject| %><br>
<%=ff.label subject.subject_full_name%><br>
<%=ff.text_field :rate %>
<%=ff.hidden_field :subject_id, :value => subject.id%><br>
<%end%>
<% end %>
<%= f.submit 'Add student'%>
<% end %>
Here`s my controller code:
class StudentsController<ApplicationController
before_action :authenticate_admin!
def new
#student = Student.new
#student.marks.build
#group = Group.find(params[:group_id])
#group.student_sort
end
def create
#group = Group.find(params[:group_id])
#student = #group.students.new(student_params)
if #student.save
redirect_to new_group_student_path
flash[:notice] = 'Студента успішно додано!'
else
redirect_to new_group_student_path
flash[:alert] = 'При створенні були деякі помилки!'
end
end
private
def student_params
params.require(:student).permit(:student_name, :student_surname, :is_payer, marks_attributes: [:id, :rate, :subject_id, :_destroy])
end
end
How can I fix it?
#student.marks.build
This line will reserve an object Mark.
If you want multi marks, May be you need something like this in new action :
#group.subjects.each do |subject|
#student.marks.build(:subject=> subject)
end
Hope useful for you.
I have a 3 models: quote, customer, and item. Each quote has one customer and one item. I would like to create a new quote, a new customer, and a new item in their respective tables when I press the submit button. I have looked at other questions and railscasts and either they don't work for my situation or I don't know how to implement them.
quote.rb
class Quote < ActiveRecord::Base
attr_accessible :quote_number
has_one :customer
has_one :item
end
customer.rb
class Customer < ActiveRecord::Base
attr_accessible :firstname, :lastname
#unsure of what to put here
#a customer can have multiple quotes, so would i use has_many or belongs_to?
belongs_to :quote
end
item.rb
class Item < ActiveRecord::Base
attr_accessible :name, :description
#also unsure about this
#each item can also be in multiple quotes
belongs_to :quote
quotes_controller.rb
class QuotesController < ApplicationController
def index
#quote = Quote.new
#customer = Customer.new
#item = item.new
end
def create
#quote = Quote.new(params[:quote])
#quote.save
#customer = Customer.new(params[:customer])
#customer.save
#item = Item.new(params[:item])
#item.save
end
end
items_controller.rb
class ItemsController < ApplicationController
def index
end
def new
#item = Item.new
end
def create
#item = Item.new(params[:item])
#item.save
end
end
customers_controller.rb
class CustomersController < ApplicationController
def index
end
def new
#customer = Customer.new
end
def create
#customer = Customer.new(params[:customer])
#customer.save
end
end
my form for quotes/new.html.erb
<%= form_for #quote do |f| %>
<%= f.fields_for #customer do |builder| %>
<%= label_tag :firstname %>
<%= builder.text_field :firstname %>
<%= label_tag :lastname %>
<%= builder.text_field :lastname %>
<% end %>
<%= f.fields_for #item do |builder| %>
<%= label_tag :name %>
<%= builder.text_field :name %>
<%= label_tag :description %>
<%= builder.text_field :description %>
<% end %>
<%= label_tag :quote_number %>
<%= f.text_field :quote_number %>
<%= f.submit %>
<% end %>
When I try submitting that I get an error:
Can't mass-assign protected attributes: item, customer
So to try and fix it I updated the attr_accessible in quote.rb to include :item, :customer but then I get this error:
Item(#) expected, got ActiveSupport::HashWithIndifferentAccess(#)
Any help would be greatly appreciated.
To submit a form and it's associated children you need to use accepts_nested_attributes_for
To do this, you need to declare it at the model for the controller you are going to use (in your case, it looks like the Quote Controller.
class Quote < ActiveRecord::Base
attr_accessible :quote_number
has_one :customer
has_one :item
accepts_nested_attributes_for :customers, :items
end
Also, you need to make sure you declare which attributes are accessible so you avoid other mass assignment errors.
If you want add info for diferent models i suggest to apply nested_model_form like this reference: http://railscasts.com/episodes/196-nested-model-form-part-1?view=asciicast.
This solution is very simple and cleanest.
I'm using the rails3-jquery-autocomplete gem my artist field on a releases form and want to make sure any new releases use the id of an existing artist if found via autocomplete or create a new artist if not found. At the moment the autocomplete gem returns artists as expected but created a new entry even if that artist exists.
My models are as follows:
class Artist < ActiveRecord::Base
has_and_belongs_to_many :releases
end
class Release < ActiveRecord::Base
has_and_belongs_to_many :artists
accepts_nested_attributes_for :artists, :reject_if => lambda { |a| a[:name].blank? }
end
And controllers:
class ArtistsController < ApplicationController
def index
#artists = Artist.find(:all, :order => :name, :group => :name)
end
def create
#release = Release.find(params[:release_id])
#artist = #release.artists.create(params[:artist])
redirect_to release_path(#release)
end
def destroy
#release = Release.find(params[:release_id])
#artist = #release.artists.find(params[:id])
#artist.destroy
redirect_to release_path(#release)
end
end
class ReleasesController < ApplicationController
autocomplete :artist, :name
def new
#release = Release.new
#release.artists.build
end
def create
#release = Release.new(params[:release])
#release.user_id = current_user.id
end
end
My routes contains:
resources :releases do
get :autocomplete_artist_name, :on => :collection
end
And finally the artist fields:
<%= f.fields_for :artists do |builder| %>
<%= render 'artist_fields', :f => builder %>
<% end %>
(The rendered fields)
<%= f.label :name, "Artist" %><br />
<%= f.autocomplete_field :name, autocomplete_artist_name_releases_path, :class => "text" %>
I suggest you watch this railscast on autcomplete associations
It clearly does what you need.
I don't want to spoil your viewing pleasure, but it all revolves around find_or_create_by
Why don't you fetch the id of the artist with
:id_element => '#artist_id' on the f.autocomplete_field?
If the artist_id param does not come back, you can create it