rails newbie here.
I want my questions/view/index to show the name of the language associated with a question, rather than the language_id.
My question model is:
class Question < ActiveRecord::Base
validates :phrase, presence: true
has_many :answers, dependent: :delete_all
belongs_to :language
end
class CreateQuestions < ActiveRecord::Migration
def change
create_table :questions do |t|
t.string :phrase
t.string :language
t.timestamps
end
end
end
class AddLanguageIdToQuestions < ActiveRecord::Migration
def change
add_column :questions, :language_id, :integer
end
end
My language model is:
class Language < ActiveRecord::Base
end
class CreateLanguages < ActiveRecord::Migration
def change
create_table :languages do |t|
t.string :name
t.timestamps
end
end
end
In my questions controller:
def index
#questions = Question.all
#language = Language.find(#questions.language_id)
end
In the questions/_form.html.erb:
<p>
<%= f.label :language_id %><br>
<%= f.select :language_id, #languages.map { |l| [l.name, l.id] }, {:prompt => 'select language'} %>
</p>
And in the questions/view/index.html.erb:
<% #questions.each do |question| %>
<li>"<%= link_to question.phrase, question %>" in <%= question.language.name %>?%></li>
<% end %>
The error I keep getting, despite trying several variations of "question.language.name" (which works just fine in the show view) is "undefined method "language_id" in the index view.
Any help would be very much appreciated.
just change your index action to:
def index
#questions = Question.all.includes(:language)
end
edit
<% #questions.each do |question| %>
<% unless question.language.nil? %>
<li>"<%= link_to question.phrase, question %>" in <%= question.language.name %>?%></li> <% end %>
<% end %>
OR
<% #questions.each do |question| %>
<li>"<%= link_to question.phrase, question %>" in <%= question.language.name unless question.language.nil? %>?%></li>
<% end %>
Both will work fine depends on you what do you want.
feel free to ask if problem continues or not solved.
Your Problem
What you were doing wrong was:
finding all questions and then this line was wrong finding all question's language at once.
#language = Language.find(#questions.language_id)
And to avoid this: better solution is to avoid N + 1 query problem using includes
ActiveRecord Associations
This sounds like a job for ActiveRecord Associations, specifically the has_many association:
ActiveRecord associations basically use a foreign_key in your database to pull relational data & append to your object. Currently, you're only focused on using a single object, without any associated data.
--
Your problem can be fixed using the following:
#app/models/question.rb
Class Question < ActiveRecord::Base
belongs_to :language
end
#app/models/language.rb
Class Language < ActiveRecord::Base
has_many :questions
end
This will allow you to call the following in your controller & view:
#app/controllers/questions_controller.rb
Class QuestionsController < ApplicationController
def index
#questions = Question.all
end
end
#app/views/questions/index.html.erb
<% #questions.each do |question| %>
<%= question.language.name %>
<% end %>
--
Bonus
You can use the .delegate method to provide you with the ability to stop the law of dementer issue:
#app/models/question.rb
Class Question < ActiveRecord::Base
belongs_to :language
delegate :name, to: :language, prefix: true #-> #question.language_name
end
#questions.language_id is incorrect because you are trying to retrieve a single id from an array. i.e. Question.all returns an array. Also, Language.find requires a single id parameter to return a single language.
What exactly are you trying to return with Language.find(#questions.language_id) ? An array of languages that have a question that belong to it?
Also, where is the _form.html.erb partial called? By default, rails scaffold will call the partial in the new and edit actions and therefore if you are trying to set #languages for the select field in the form partial, then you would not do it in the index action. Also, Consider using a collection select for this field as it is for an association.
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 want a User to be able to answer all questions that are assigned to them, in an Answer model. Now I'm trying to create a form that allows me to loop through the questions a User have assigned to them, and answer them in an Answer model.
In the answer model I save the reply, and the question id. However this requires multiple saves in one form, which I'm unable to do.
Model associations look like this:
User
has_many :answers
has_many :questions, through: :question_participants
Answer
belongs_to :user
belongs_to :question
now I'm trying to create an Answer#new form like this:
<%= form_for #answer do |f| %>
<% #questions.each do |question| %>
<h3><%= question.name %></h3>
<%= f.hidden_field :question_id, value: question.id %>
<%= f.text_field :reply, class: 'form-control' %>
<% end %>
<%= f.submit 'Send inn', class: 'btn btn-success' %>
<% end %>
and thus hoping it will allow me to save multiple columns in one, but that doesn't work. It only saves the last column, no matter what.
My answers controller:
class AnswersController < ApplicationController
def new
#questions = current_user.questions
#answer = current_user.answers.new
end
def create
#questions = current_user.questions
#answer = current_user.answers.new(answer_params)
if #answer.save
redirect_to answers_path
else
render 'new'
end
end
private
def answer_params
params.require(:answer).permit(:reply, :question_id)
end
end
What you're looking for is accepts_nested_attributes_for:
This should work:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :answers
has_many :questions, through: :answers
accepts_nested_attributes_for :answers
#this will have to be populated on user create
before_create :build_answers
private
def build_answers
questions = Question.find [1,3,4,6]
questions.each do |question|
user.build_answer(question: question)
end
end
end
#app/models/answer.rb
class Answer < ActiveRecord::Base
#columns id | user_id | question_id | response | created_at | updated_at
belongs_to :user
belongs_to :question
end
#app/models/question.rb
class Question < ActiveRecord::Base
has_many :answers
has_many :users, through: :answers
end
This will give you the ability to do the following:
#config/routes.rb
resources :answers, only: [:edit, :update]
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def edit
#questions = current_user.questions
end
def update
#answers = current_user.answers.update answer_params
end
private
def answer_params
params.require(:answer).permit(:response) #-> question_id and user_id set on create, don't need to be changed
end
end
This will allow you to use the following form:
#app/views/answers/edit.html.erb
<%= form_tag answers_update_path, method: :patch do |f| %>
<% #questions.each do |question| %>
<%= f.fields_for "answers[]", question do |qf| %>
<%= qf.label question.title %>
<%= qf.text_field :response %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
--
Typically, you'd use the accepts_nested_attributes_for with the nested model. However, since you just want multiple answer responses, you can use the above.
The bugs in this would likely be in the strong params, or in the form declaration (IE current_user.questions). If you reply with information, I'll write some upates
Ref: Multiple objects in a Rails form
I've tried every commenting gem out there and they pretty much all suck.
Take a look at this question I previously asked:Finding user total votes recieved on posts by other users
Per recommendation, I've decided to build my own commenting system from scratch.
Here is the goal:
To have a post model, user model(using devise), comment model.
The users can create comments. These comments are votable. The amount sum of votes users receive on the comments they made is their score or karma.
How do I implement something like this?
So far this is what I have:
I ran
rails generate model Comment commenter:string body:text post:references
The migration
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :commenter
t.text :body
t.references :post
t.timestamps
end
add_index :comments, :post_id
end
end
In my comment model
class Comment < ActiveRecord::Base
belongs_to :post
attr_accessible :commenter, :body
end
In my post model
class Post < ActiveRecord::Base
has_many :comments
end
My routes file
resources :posts do
resources :comments
end
My comments controller
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment].permit(:commenter, :body))
redirect_to post_path(#post)
end
end
Within my posts show view
<h2>Comments</h2>
<% #post.comments.each do |comment| %>
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
<% end %>
<%= form_for([#post, #post.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</p>
<p>
<%= f.label :body %><br />
<%= f.text_area :body %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Edit Post', edit_post_path(#post) %> |
<%= link_to 'Back to Posts', posts_path %>
I really need help here: Instead of picking a commenter name, I need the controller to require the user to be logged in, and pass the current user as the commentor of the comment. How do I implement in place editing of a comment?
I then need to use either one of these gems to make the comments votable:
https://github.com/bouchard/thumbs_up
https://github.com/ryanto/acts_as_votable
Lastly I need to be able to calculate the total votes a given user has received on all of their posted comments. Something like #user.comments.votes.size
To handle assigning the current_user to the comment, first you'll need to change the commenter column to an id that references Users (I would also rename it to commenter_id). So, you'll want to generate the model like so:
rails generate migration ChangeCommentsCommenterToCommenterId
# db/migrate/<timestamp>_change_comments_commenter_to_commenter_id.rb
class ChangeCommentsCommenterToCommenterId < ActiveRecord::Migration
def change
remove_column :comments, :commenter
add_column :comments, :commenter_id, :integer, null: false
add_index :comments, :commenter_id
end
end
Or, regenerate the model from scratch:
rails generate model Comment commenter_id:integer:index body:text post:references
Note that I've added an index to the column. In your Comment model:
# app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :commenter, class_name: 'User'
attr_accessible :body
end
Note that, since we're using belongs_to here, when you send the commenter message to an instance of Comment, you'll get back an instance of User.
Next you'll need to update your controller to make the proper user assignment. I would also recommend a bit of refactoring to private methods to make the implementation more expressive of the domain:
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
post.comments.create(new_comment_params) do |comment|
comment.commenter = current_user
end
redirect_to post_path(post)
end
private
def new_comment_params
params.require(:comment).permit(:body)
end
def post
#post ||= Post.find(params[:post_id])
end
end
Since you're redirecting to post_path, I've assumed that you don't need to keep the #comment instance variable.
Finally, you'll want to remove the commenter field from the form in the view. Does that do the trick?
[EDIT: Adding this section to address the question about voting...]
I'm not completely clear on exactly what parts of the voting you're looking for help with, but at least from a high-level perspective, I'd guess you'd probably want a VotesController that's accessible from a nested route:
# config/routes.rb
...
resources :comments do
resources :votes, except: :index
end
Hope this helps!
I have the following line of code which seems to work okay.
<% current_user.blockedshows.map(&:tvshows).each_with_index do |blocked, index| %>
However, when I call it on blocked.title and image_tag(blocked.image), (full code below) I am getting the following error: undefined method title for <ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Tvshow:0x007fd4e24b9448>
View
<%= blocked.title %>
<%= image_tag(blocked.image) %> </br>
Tweets containing the following keywords will be removed from your timeline: </br>
<%#keywords = blocked.phrases.map(&:text) %>
<%=#keywords %> </br>
Blockedshow Model
class Blockedshow < ActiveRecord::Base
has_many :tvshows
belongs_to :user
end
Tvshow Model
class Tvshow < ActiveRecord::Base
has_many :phrases
belongs_to :blockedshow
end
Tvshow Table
class CreateTvShows < ActiveRecord::Migration
def change
create_table :tvshows do |t|
t.string :title
t.string :image
t.timestamps
end
end
end
That's because tvshows itself is an ActiveRecord association object.
You need to loop over it also:
current_user.blockedshows.map(&:tvshows).each_with_index do |blocked, index|
blocked.each do |b|
<%= b.title %>
end
end
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