Multiple Checkbox in Ruby - ruby-on-rails

I'm trying to create an "ingredient" checkbox list derived from my "recipes", I'd like for the values to be saved in the database so that when it's checked and I refresh the page, it still shows as checked.
The error says "uninitialized constant #Class:0x00007f8f2d360830::Parties"
Here's an example of what i am trying to do
Controller:
# parties_controller.rb
def ingredients
#party = Party.find(params[:party_id])
#party_recipe = #party.recipes
#party_recipe.each do |recipe|
#ingredients = recipe.ingredients
end
The models:
Party model
#party.rb
class Party < ApplicationRecord
has_many :party_recipes
has_many :recipes, through: :party_recipes
end
Recipe model
#recipe_ingredient.rb
class RecipeIngredient < ApplicationRecord
belongs_to :recipe
belongs_to :ingredient
end
Ingredient model
#ingredient.rb
class Ingredient < ApplicationRecord
has_many :recipe_ingredients
has_many :recipes, through: :recipe_ingredients
end
Form:
#ingredients.html.erb
<% form_for "/parties/#{#party.id}/ingredients" do |f| %>
<% Parties::Recipes::Ingredients.each do |ingredient| %>
<%= check_box_tag(ingredient) %>
<%= ingredient %>
<% end %>
<% end %>
Schema:
create_table "ingredients", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "parties", force: :cascade do |t|
t.string "title"
t.string "address"
t.bigint "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "theme"
t.date "date"
t.integer "attendancy"
t.integer "appetizers"
t.integer "mains"
t.integer "desserts"
t.string "status", default: "pending"
t.index ["user_id"], name: "index_parties_on_user_id"
end
create_table "party_recipes", force: :cascade do |t|
t.bigint "recipe_id", null: false
t.bigint "party_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["party_id"], name: "index_party_recipes_on_party_id"
t.index ["recipe_id"], name: "index_party_recipes_on_recipe_id"
end
create_table "recipe_ingredients", force: :cascade do |t|
t.bigint "recipe_id", null: false
t.bigint "ingredient_id", null: false
t.string "amount"
t.boolean "included", default: false
t.index ["ingredient_id"], name: "index_recipe_ingredients_on_ingredient_id"
t.index ["recipe_id"], name: "index_recipe_ingredients_on_recipe_id"
end
create_table "recipes", force: :cascade do |t|
t.string "title"
t.text "description"
end
add_foreign_key "party_recipes", "parties"
add_foreign_key "party_recipes", "recipes"
add_foreign_key "recipe_ingredients", "ingredients"
add_foreign_key "recipe_ingredients", "recipes"
I'm not entirely sure where exactly needs to be corrected, any help appreciated, thank you so much!

Well the error message is correct, you don't have any model called Parties, in fact in Rails, models are always singular, camel-case. So that explains the error message.
However that won't fix your problem! The iterator in the view should be
<% #ingredients.each do |ingredient| %>
<%= check_box_tag(ingredient) %>
<%= ingredient %>
<% end %>
Because I think you are trying to populate an #ingredients variable in your controller. However it still won't work, b/c the value of the #ingredients variable is not being correctly assigned...
Personally I much prefer the "fat model skinny controller" design style for Rails. So I would have a PartiesController#ingredients method that looks like this:
# parties_controller.rb
def ingredients
#party = Party.find(params[:party_id])
#ingredients = #party.ingredients
end
then in your Party model:
# app/models/party.rb
def ingredients
recipes.map(&:ingredients).flatten
end
Why do it this way? Well you're just getting started with Rails, but eventually (soon hopefully) you'll be writing tests, and it's much much easier to write tests on models than controllers.
Now, there could well be some other issues in your code, but try my suggestions and see where that gets you.

#Les Nightingill's answer should work well for organizing your controller and model! Regarding when you click refresh and the value of the boxes are saved either;
Set up some listeners in javascript and send a request to your update controller method every time there is a value change for one of your check boxes.
Or add a save button at the bottom of your form that points to your update controller method to save the values of the checkboxes. Something like:
<%= submit_tag "Save", data: { disable_with: "Saving..." } %>
https://api.rubyonrails.org/v5.2.3/classes/ActionView/Helpers/FormTagHelper.html#method-i-submit_tag

Related

How to implement counter with buttons to a post in a polymorphic associations?

I have an app where I can make post and create comments to them. And I need to implement 2 buttons (like and unlike) in my view to every post(tweet) and comment to this tweet. Also I have 2 routes to my likes: post and destroy: (all routes are here)
resource :user_profiles, only: %i[edit update]
resources :tweets
resources :comments, only: %i[create destroy]
resources :likes, only: %i[create destroy]
root to: 'home#index'
But also I have polymorphic associations:
class Like < ApplicationRecord
belongs_to :likable, polymorphic: true
belongs_to :user
end
class Tweet < ApplicationRecord
belongs_to :user
has_many :likes, as: :likable
has_many :comments, dependent: :destroy
end
class Comment < ApplicationRecord
has_many :likes, as: :likable
belongs_to :tweet
belongs_to :user
end
I wrote some code in my tweet.preview
<% if tweet.likes.find_by(user_id: current_user.id)%>
<%= button_to 'Unlike', like_path(likable_id: tweet.id, likable_type: tweet.class.name, user_id: current_user.id), method: :delete %>
<% else %>
<%= button_to 'Like', likes_path(likable_id: tweet.id, likable_type: tweet.class.name, user_id: current_user.id), method: :post%>
<% end %>
But I don't know how to implement like controller to count likes and display 2 buttons with counter to view.
My db.schema is here:
create_table "comments", force: :cascade do |t|
t.string "content"
t.integer "user_id"
t.integer "tweet_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "likes", force: :cascade do |t|
t.integer "user_id"
t.string "likable_type"
t.integer "likable_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "tweets", force: :cascade do |t|
t.string "content"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_tweets_on_user_id"
end
create_table "user_profiles", force: :cascade do |t|
t.string "username"
t.string "first_name"
t.string "last_name"
t.date "birthday"
t.string "bio"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_user_profiles_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
Thanks in advance!
Well you just need to pass the parent of the future like record (whereas it is a post or a comment) to the create action of the like controller.
You need to add a link to your buttons such as : like_path(parent_type: "comment", parent_id: #comment_id), method: "post"
(here #comment_id may not be an instance variable, especially if you loop through all comment of a specific tweet, it may instead look like comment.id if in your loop you did something like #tweet.comments.each do |comment|)
Then in your like controller, you can just assign them to the record.
I assume here that the user id "is already known" and you create the like from the user record.
#like = current_user.likes.new(likable_id: params[:parent_id], likable_type: params[:parent_type])
#like.save
So most of the job is made in your view by creating the correct link for your different buttons...
So i'm not sure about your setup. Are you using a gem?
So the secret to do this is to create two seperated paths:
resources :likes do
member do
get :like
get :unlike
end
end
inside your controller, you can then execute the logic inside each method:
def like
//find the user
//follow the user
//redirect him
end
def unlike
//find the user
//unfollow the user
//redirect him
end
Also, you can then simply use your buttons for each like/unlike path:
<% if tweet.likes.find_by(user_id: current_user.id)%>
<%= button_to 'Unlike', likes_unlike_path(likable_id: tweet.id, likable_type: tweet.class.name, user_id: current_user.id), method: :delete %>
<% else %>
<%= button_to 'Like', likes_like_path(likable_id: tweet.id, likable_type: tweet.class.name, user_id: current_user.id), method: :post%>
<% end %>
Now, to display the sizes of a like, you could retrieve the amount of users that have liked the tweet:
<h3>People who liked this tweet: <%= likes.count %> </h3>
obviously, only those people who have liked something will be displayed. If you also need a negative number, you could do two things: Either create a join table for your likes or add a new integer and call it "number_likes"
Then, inside your like method, simply increase the number_likes integer by 1. Whenvever someone clicks on the like link, the number will increase.
Inside the unlike method, simply substract by 1 whenever unlikes something. This should give you more or less the same result, but it would also allow you to add a negativ like number.
def like
number_likes += 1
//your previous logic here
end
def unlike
number_likes -=1
end
in your view, you could then simply get the tweet.likes.number_likes to display the amount of likes.
If something is still unclear, let me know!
Happy coding!

Rails - Nested Forms

I'm having a problem with nested forms, I'm new to rails so probably I'm doing something wrong, so I hope you can help me. Thanks in advance.
I have a model Poi (Points of Interest), a model PoiDescription and a model DescriptionType. An instance of Poi has many PoiDescriptions and a DescriptionType has many PoiDescriptions. What I want is this: when I'm creating a new Poi, I want to create multiple descriptions to it. A description has a category associated to it and there can only be one description for each category(ex: 10 categories = 10 descriptions). So here is my code:
Poi model
has_many :poi_descriptions
accepts_nested_attributes_for :poi_descriptions
PoiDescription model
belongs_to :poi
belongs_to :description_type
DescriptionType model
has_many :poi_descriptions
Poi controller
def new
#poi = Poi.new
#poi.poi_descriptions.build
#availableType = DescriptionType.where.not(id: #poi.poi_descriptions.pluck(:description_type_id))
end
def poi_params
params.require(:poi).permit(:name, :image, :longitude, :latitude, :monument_id, :beacon_id, poi_descriptions_attributes: [:description, :description_type_id, :id])
end
routes
resources :pois do
resources :poi_descriptions
end
resources :description_types
Poi _form
<%= form_with model: #poi do |f| %>
...
<%= f.fields_for :poi_descriptions do |p| %>
<%= p.collection_select :description_type_id, #availableType,:id,:name %>
<%= p.text_area :description %>
<% end %>
...
schema
create_table "description_types", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "poi_descriptions", force: :cascade do |t|
t.text "description"
t.bigint "poi_id"
t.bigint "description_type_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["description_type_id"], name: "index_poi_descriptions_on_description_type_id"
t.index ["poi_id"], name: "index_poi_descriptions_on_poi_id"
end
create_table "pois", id: :serial, force: :cascade do |t|
t.text "name"
t.float "longitude"
t.float "latitude"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "monument_id"
t.integer "beacon_id"
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
t.index ["beacon_id"], name: "index_pois_on_beacon_id"
t.index ["monument_id"], name: "index_pois_on_monument_id"
end
Now, the problem. Every time I try to create a new Poi I have this error:
Does anyone know why this is happening? Thanks.
P.S: Sorry for the long post :)
I've found the solution. Before having the PoiDescription model I had an attribute 'description' on the Poi model and I've put a validation on that attribute. When I changed the model, I forgot to remove that validation and that was the origin of my problem.

How to select all objects that have instances of another object (has_many and has_one association)?

I have two models, User and Course.
User has_many Courses.
Course has_one User.
Right now I'm grabbing all users and displaying them on courses index page, however, I've realised that I should only be displaying users that have courses. I'm unsure how to do this?
Here is my index method from courses controller:
#courses_controller.rb
def index
#courses = Course.all
#users = User.all
end
Here are my models:
# user.rb
class User < ApplicationRecord
# User has many courses
has_many :courses, dependent: :destroy
end
# course.rb
class Course < ApplicationRecord
has_one :user
validates :user_id, presence: true
end
And my schema:
ActiveRecord::Schema.define(version: 20170505114247) do
create_table "courses", force: :cascade do |t|
t.string "name"
t.string "prerequisite"
t.text "description"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "picture"
t.index ["user_id", "created_at"], name: "index_courses_on_user_id_and_created_at"
t.index ["user_id"], name: "index_courses_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.index ["email"], name: "index_users_on_email", unique: true
end
end
Since there is a one-to-many association between users and courses then on the courses index page when you iterate over each course you can include the user information. You don't need to include #users = User.all in the controller. So your iterator might look something like this
In controller:
#courses = Course.includes(:user).all #to avoid n+1 query
Your view:
# app/views/courses/index.html.erb
<% #courses.each do |course| %>
<%= course.name %>
<%= course.description %>
<%= course.user.name %>
<% end %>

Counter Cache customizing for zeros and large numbers

I'm still learning rails so any help you can provide would be super helpful. I've set a count for my likes on my book app. Thus, every time a user likes a book - the number increases by one or decreases if the unlike it. However, if no one has liked a book yet - a 0 appears. I'd like that to be blank so that only when a user has liked it will the number appear. I've listed all my relevant code below. Thank you so much.
Schema.rb
create_table "books", force: :cascade do |t|
t.string "title"
t.integer "user_id"
t.integer "book_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
t.integer "likes_count", default: 0, null: false
end
create_table "likes", force: :cascade do |t|
t.integer "user_id"
t.integer "book_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Book.rb
class Book < ApplicationRecord
has_many :likes, :counter_cache => true
has_many :users, through: :likes
belongs_to :user
end
Likes.rb
class Like < ApplicationRecord
belongs_to :book, :counter_cache => true
belongs_to :user
end
Likes Count Migration
class AddLikecountsToBook < ActiveRecord::Migration[5.0]
def change
add_column :books, :likes_count, :integer, :null => false, :default => 0
end
end
With associations in rails you get several interogation methods such as .any? and .none? which can be used to create conditional expressions.
<% if book.likes.any? %>
<%= number_to_human(book.likes.size) %>
<% end %>
# or
<%= number_to_human(book.likes.size) unless book.likes.none? %>
This uses the counter cache as well to avoid n+1 queries.
If you do not want your view to display 0 you could add a if statement in your view.
<% if #votes == 0 %>
be the first to rate this book
<% else %>
<%= #votes %>
<% end %>
Or when returning the variable to the view from the controller
def
if #votes == 0
#votes = ''
end
end

Rails - One-direction table association - accessing data issue

I have two models:
class Order < ActiveRecord::Base
belongs_to :user
has_one :order_type
end
class OrderType < ActiveRecord::Base
belongs_to :order
end
my schema.rb:
create_table "order_types", force: :cascade do |t|
t.string "ort_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "orders", force: :cascade do |t|
t.string "ord_name"
t.date "ord_due_date"
t.integer "user_id"
t.integer "ordertype_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "orders", ["ordertype_id"], name: "index_orders_on_ordertype_id"
add_index "orders", ["user_id"], name: "index_orders_on_user_id"
There is only one-direction association between them. The Order model has a column "ordertype_id" that links to the appropriate order_type.
My question is, what is the best practice to access the ort_name value for each #order in a view.
Currently, I am using:
<p>
<strong>Ord type:</strong>
<% OrderType.where(id: #order.ordertype_id).each do |t| %>
<%= t.ort_name %>
<% end %>
</p>
This solution results in many code repetitions. How I should change that? Can somebody advise, as I am not so experienced yet?
I tried this code, but it did not work:
#orders.order_type
There are many problems which you should address. It's ok to be a beginner, just take yourself time to learn and improve.
Schema
First off, your schema is set up badly. If you want to limit the order type to certain values, you should do this with a validation.
class Order
TYPES = %w[foo bar three four five]
validates :order_type, inclusion: { in: TYPES }
end
This way, you can easily add values in the future, and remove the complexity of adding a new model and its relations.
Column Names
Secondly, you should revise your column names. ord_name and ord_due_date is bad, it leads to ugly calls like order.ord_name. You should drop the prefix ord, it's superfluous.
Both steps would lead to this schema.rb
create_table "orders", force: :cascade do |t|
t.string "name"
t.date "due_date"
t.integer "user_id"
t.string "order_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Logic placement
My final advice is to never call queries from your view. Logic should always be in the controller / model & passed to the view via instance variables.
This is a big no no in rails:
<% OrderType.where(id: #order.ordertype_id).each do |t| %>
...
<% end %>
In the end, accessing the type is simply accomplished with:
#order.order_type
Update your Order model to this:
class Order < ActiveRecord::Base
belongs_to :user
has_one :order_type, foreign_key: 'ordertype_id`
end
then order_type should be easily accessible:
#order.order_type.ort_name

Resources