I'm making simple CRUD and the current goal is to add data. However, I found that I can't add any data, and the terminal log also shows "[Webpacker] Everything's up-to-date. Nothing to do", which means there is no error message.
According to my design in the controller, the new data must have failed, so I stopped at new.html.erb. I'm guessing it has something to do with the model's relationship.
This is model User
class User < ApplicationRecord
has_many :reviews
has_many :recipes, through: :reviews
end
This is the model Recipe
class Recipe < ApplicationRecord
has_many :reviews
belongs_to :user
end
This is model Review
class Review < ApplicationRecord
belongs_to :user
belongs_to :recipe
end
This is the RecipeController
class RecipesController < ApplicationController
def index
#recipes = Recipe.all
end
def new
#recipe = Recipe.new
end
def create
#recipe = Recipe.new(recipe_params)
if #recipe.save
redirect_to recipes_path, notice: "Successful!"
else
render :new
end
end
private
def recipe_params
params.require(:recipe).permit(:title, :money)
end
end
this is the web page
<h1>Add New One</h1>
<%= form_for(#recipe) do |r| %>
<%= r.label :title, "Title" %>
<%= r.text_field :title%>
<%= r.label :money, "Budget" %>
<%= r.text_field :money %>
<%= r.submit %>
<% end %>
<%= link_to "Back to list", recipes_path %>
You should first add a callback to ensure that only signed in users can create recipes (unless you actually want to let anomynous users create/update/delete recipies).
For example with Devise you would use its authenticate_user! helper which will bail and redirect to the sign in path if the user is not authenticated:
class RecipesController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
# ...
end
If you're reinventing the authentication wheel you should create a similiar method which is used to prevent access.
You would then initialize the resource off the current user:
class RecipesController < ApplicationController
before_action :authenticate_user!, except: [:show, :index]
def create
#recipe = current_user.recipes.new(recipe_params)
if #recipe.save
redirect_to recipes_path, notice: "Successful!"
else
render :new
end
end
end
Here I am assuming that you have a current_user method which will retrieve the user based on an id stored the session.
Since you have an indirect assocation this will create a row in the reviews table with the users id and the recipe id as the record in the recipies table.
You also want to display the validation errors in the form so that the user gets feedback.
You are probably right that it is a validation error related to the belongs_to relationship. You should display validation errors for your form as described here https://guides.rubyonrails.org/getting_started.html#validations-and-displaying-error-messages
Related
I am attempting to develop a model in which a user can add the recipe they are viewing to an existing menu of recipes they have created, similar to adding a song to a custom playlist. I believe I have the models set up correctly (using a many to many through relationship) however I am unsure how to go about the adding of the actual records to a selected collection. Any guidance would be helpful. My code is as below.
Menus Controller
class MenusController < ApplicationController
before_action :set_search
def show
#menu = Menu.find(params[:id])
end
def new
#menu = Menu.new
end
def edit
#menu = Menu.find(params[:id])
end
def create
#menu = current_user.menus.new(menu_params)
if #menu.save
redirect_to #menu
else
render 'new'
end
end
def update
#menu = Menu.find(params[:id])
if #menu.update(menu_params)
redirect_to #menu
else
render 'edit'
end
end
def destroy
#menu = Menu.find(params[:id])
#menu.destroy
redirect_to recipes_path
end
private
def menu_params
params.require(:menu).permit(:title)
end
end
Menu Model
class Menu < ApplicationRecord
belongs_to :user
has_many :menu_recipes
has_many :recipes, through: :menu_recipes
end
menu_recipe Model
class MenuRecipe < ApplicationRecord
belongs_to :menu
belongs_to :recipe
end
Recipe Model
class Recipe < ApplicationRecord
belongs_to :user
has_one_attached :cover
has_many :menu_recipes
has_many :menus, through: :menu_recipes
end
User Model
class User < ApplicationRecord
has_secure_password
has_one_attached :profile_image
has_many :recipes
has_many :menus
end
You can do something like :
def add_recipe_to_menu
menu = current_user.menus.find params[:id]
recipe = current_user.recipes.find params[:recipe_id]
menu.recipes << recipe
end
It will add a viewing recipe to existing menu of recipes.
First make sure you build the new record off the user:
class MenusController < ApplicationController
# make sure you authenticate the user first
before_action :authenticate_user!, except: [:show, :index]
def new
#menu = current_user.menus.new
end
def create
#menu = current_user.menus.new(menu_attributes)
# ...
end
end
Then we can just add a select to the form where the user can select from his recipes:
# use form_with in Rails 5.1+
<%= form_for(#menu) do |f| %>
... other fields
<div class="field">
<%= f.label :recipe_ids %>
<%= f.collection_select :recipe_ids, f.object.user.recipies, :id, :name, multiple: true %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
f.object accesses the model instance wrapped by the form builder.
recipe_ids is a special setter/getter created by ActiveRecord for has_many associations. As you may have guesses it returns an array of ids and lets the association be set with an array of ids - automatically inserting/deleting rows in the join table in the process.
You then just need to whitelist the recipe_ids param:
def menu_attributes
params.require(:menu)
.permit(:foo, :bar, recipe_ids: [])
end
recipe_ids: [] whitelists an array of permitted scalar types. Since this is a hash option it must be listed after any positional arguments to be syntactically valid.
rb(main):003:0> params.require(:menu).permit(:foo, recipe_ids: [], :bar)
SyntaxError: (irb):3: syntax error, unexpected ')', expecting =>
I have someone of a unique problem. I have 3 tables in the database that I need to populate with data. All tables are in relation to each other. The first table's info will be static and populated from a hash. The second table is the table that is usually targeted with data.
I am having a tough time trying to add data into the second table using strong parameters. I get an error param is missing or the value is empty: entries
Modles:
client.rb
class Client < ActiveRecord::Base
has_many :entries
end
Entry.rb
class Entry < ActiveRecord::Base
belongs_to :client_name
has_many :extra_data
end
extra_data.rb
class ExtraData < ActiveRecord::Base
belongs_to :entries
end
class ClientsController < ApplicationController
before_action :set_client, only: [:show, :update, :destroy, :edit]
# submit for all intended purposes.
#
def new
#entries = Entry.new()
end
def create
#client = Client.new(CLEINT_ATTR)
if #client.save
#entries = Entry.new(submit_params)
redirect_to action: :index
else
flash.alert "you failed at life for today."
redirect_to action: :index
end
end
.
.
.
private
def submit_params
params.require(:entries).permit( :full_name,:email,:opt_in )
end
def set_client
#client = Client.find(params[:id])
end
end
form
<%= simple_form_for(:client, url: {:controller => 'clients', :action => 'create'}) do |f| %>
<%= f.input :full_name %>
<%= f.input :email %>
<%= f.input :opt_in %>
<%= f.button :submit, class: "btn-primary" %>
<% end %>
Routes:
Rails.application.routes.draw do
resources :clients do
resources :entries do
resources :extra_data
end
end
root 'clients#index'
end
In the Database Client data goes in with out a problem. I am having a problem getting the data from the form itself.
This answer is the culmination of a few different parts.
I figured out I was not saving any data into the model. So I needed to make another if statement.
def create
#client = Client.new(CLEINT_ATTR)
if #client.save
#entries = Entry.new(submit_params)
if #entries.save
flash[:alert] = "Failure! everything is working."
redirect_to action: :index
else
flash[:alert] = "Success! at failing."
end
else
flash[:alert] = "you failed at life for today."
redirect_to action: :thanks
end
end
Also changing the form from :entries Helped. I also had a typo in my permit statment. I had :opt_in when I needed to use :optin Thanks #tmc
Im new to rails and am having difficulty creating a profile by a devise user
here is the ProfileController:
class ProfilesController < ApplicationController
before_action :set_profile, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def index
#profiles = Profile.all
end
def new
#profile = current_user.build_profile
end
def create
#profile = current_user.build_profiles(profile_params)
redirect_to profiles_path
end
def show
end
def edit
end
def update
#profile.update(profile_params)
redirect_to(profiles_path(#profile))
end
def destroy
#profile.destroy
redirect_to profiles_path
end
private
def profile_params
params.require(:profile).permit(:university, :degree)
end
def set_profile
#profile = Profile.find(params[:id])
end
end
When i run the rails server i can submit the form but nothing is getting stored in the model 'Profile'
here is the index.html.erb where the data should appear:
<h2>profiles</h2>
<% #profiles.each do |profile| %>
<%= link_to profile do %>
<%= profile.university %>
<% end %>
<%= profile.degree %>
<% end %>
user.rb file:
has_one :profile
and the profile.rb file:
belongs_to :user
nothing seems to be getting saved to the profile model and nothing is getting displayed on the index.html.erb. Also i have created a migration to store user_id in the profile model.
Thank for your help
By far the best way to create a profile for a user is to build it when the User object is created:
#app/models/user.rb
class User < ActiveRecord::Base
has_one :profile
before_create :build_profile
accepts_nested_attributes_for :profile
end
#app/models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
end
This will build a blank profile each time a new user is created. It means that each user will only ever have one profile, which they'll be able to populate & edit.
In regards your issue, there are several points:
Don't list "profiles" with an index, list users & pull their profile data
If managing a profile, nest it under the associative user model
Here's how to do that:
# config/routes.rb
resources :users, only: :index
resource :profile, only: [:show, :update]
#app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
def show
end
def update
redirect_to :show if current_user.update profile_params
end
private
def profile_params
params.require(:user).permit(profile_attributes: [:name])
end
end
#app/views/profiles/show.html.erb
<%= form_for current_user, url: profile_path do |f| %>
<%= f.fields_for :profile do |p| %>
<%= p.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>
Update
My post above is exactly what we do.
The way it works is very simple -- when a User is created (IE they have gone to the trouble of filling out their details), the Rails backend automatically creates a blank Profile object.
This does several things:
Always makes sure you have a Profile for each user (you don't have to go to the bother of making them "create" a profile).
Gives you the ability to validate only inputted data on a created Profile (not having to guess whether it's already been done).
--
If you're getting undefined method build_profile, it means your associations are incorrect.
All singular associations have build_[association] as defined instance methods. The code I provided fires build_profile for a has_one association. The only time it would be "undefined" would be if the association was plural
--
Update
This suggests a routing error.
Considering it appears at root, I think the problem is here:
#app/views/layouts/application.html.erb
<%= link_to "Profile", profile_path %>
You don't have edit_profile_path -- it should just be profile_path
It needs a save call for the profile which you build in crate method.
e.g.
def create
#user = User.new(user_params)
if #user.save
redirect_to root_url
else
render :new
end
end
if condition checks is data save correct or not. And change example's User model to your Profile model
BTW, just careful with the plural, I think it should use 'build_profile' instead 'build_profiles'
I have my app setup where users can write reviews for a movie. What I'd like to do is limit the user to create only one review per movie. I've managed to accomplish this in my reviews controller as so:
class ReviewsController < ApplicationController
before_action :has_reviewed, only [:new]
....
def has_reviewed?
if Review.where(user_id: current_user.id, movie_id: #movie.id).any?
redirect_to movie_reviews_path
flash[:notice] = "You've already written a review for this movie."
end
end
end
Where I'm now having trouble is translating this same logic into my index view template with the helper methods of Devise and CanCanCan at my disposal.
<% if user_signed_in? && ... %> # current_user has already created a review for this movie
<%= link_to "Edit Review", edit_movie_review_path(#movie, review) %>
<% else %>
<%= link_to "Write a Review", new_movie_review_path %>
<% end %>
Also: Is there any way to improve the lookup in my has_reviewed? method? I feel like there's a better way to write it but can't determine the most appropriate fix.
Why not use a validation:
#app/models/review.rb
class Review < ActiveRecord::Base
validates :movie_id, uniqueness: { scope: :user_id, message: "You've reviewed this movie!" }
end
This is considering your review model belongs_to :movie
You could also use an ActiveRecord callback:
#app/models/review.rb
class Review < ActiveRecord::Base
before_create :has_review?
belongs_to :user, inverse_of: :reviews
belongs_to :movie
def has_review?
return if Review.exists?(user: user, movie_id: movie_id)
end
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :reviews, inverse_of: :user
end
Is there any way to improve the lookup in my has_reviewed? method?
def has_reviewed?
redirect_to album_reviews_path, notice: "You've already written a review for this album." if current_user.reviews.exists?(movie: #movie)
end
Why not make a has_reviewed? method on your User class?
e.g.
def has_reviewed?(reviewable)
# query in here
end
Then you should be able use that just fine in your controller and your views.
You will want to do this for both new and create. Otherwise a savvy user would be able to run a post that would get past your new action.
I would put the link_to in either a helper or a presenter object. It would generally look like this.
def create_or_edit_review_path(movie, current_user)
return '' if current_user.blank?
if current_user.review.present?
#Generate review edit link
else
#generate new link
end
end
After that in all of your views it would just be
<%= create_or_edit_review_path(#movie, current_user) %>
Then in your controller for both new and create you could do either a before action or just redirect on each.
before_action :enforce_single_review, only: [:create, :new]
def enforce_single_review
if current_user.review.present?
redirect_to review_path(current_user.review)
end
end
Here's what I came up with:
I created an instance method to retrieve a user's movie review using the find_by method on the Review model:
class User < ActiveRecord::Base
....
def movie_review(album)
Review.find_by(user_id: self, album_id: album)
end
end
This method also comes in handy when setting up my callback:
class ReviewsController < ApplicationController
before_action :limit_review, only: [:new, :create]
....
private
def limit_review
user_review = current_user.movie_review(#movie)
if user_review.present?
redirect_to edit_movie_review_path(#movie, user_review)
end
end
end
Created a helper method for showing the appropriate link to edit or create a review. Big thanks to Austio and his suggestion:
module ReviewsHelper
def create_or_edit_review_path(movie)
user_review = current_user.movie_review(movie) if user_signed_in?
if user_signed_in? && user_review.present?
link_to "Edit review", edit_movie_review_path(movie, user_review)
else
link_to "Write a review", new_movie_review_path
end
end
end
And at last this is how I call the helper in my view template(s):
....
<%= create_or_edit_review_path(#album) %>
I have been trying to build an app offering discounted vacation trips such that:
(1) a user(travel agent) can compose a trip by combining hotels (hotel chains) and cities
(2) a user(regular user) can review hotels and cities, s/he has already visited.
(3) another user can evaluate how good the deal is with respect to the country and hotel the travel agent will have him/her stay.
The models look like this
class User < ActiveRecord::Base
has_many :trips
has_many :reviews
end
class Trip < ActiveRecord::Base
belongs_to :user
belongs_to :hotel
belongs_to :city
end
class Hotel < ActiveRecord::Base
belongs_to :city
has_many :reviews, as: :reviewable
end
class City < ActiveRecord::Base
has_many :hotels
has_many :reviews, as: :reviewabel
end
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :reviewable, polymorphic: true
end
The problem is I can figure out how to create the controllers for Hotel and City because they are only created in the context of a makeshift trip. I checked the rails casts on nested forms and the use of accepts_nested_attributes_for but I can't seem to get it right.
Note: the reason why I separated the hotels and the cities is to be able to retrieve the reviews independently. Such that Say I enjoyed my stay at the Four Seasons in Toronto but not the one in NY. - because of the cities/hotels (=> accommodating the case where I didn’t enjoy it because the hotel was crap and the one where I didn’t because the city was)
Note 2: I understand it doesn’t make much sense to seperate hotels and cities in this example - I made a mistake in self-appointing the tutorial. But the problem has been haunting me, what if it was an delivery order instead with entree/meal/dinner instead of hotels and cities, or restaurant chains and neighborhoods.
Any help is appreciated. Thank you
Edit
Edited after Settheline’s comment.
I mean the create actions for cities and hotels only exist in the context of a Trip create action.
Trip has 2 attributes: title & description: It’s only then that I “log” the itinerary. Here’s what my controllers look like to give you a better idea
class TripsController < ApplicationController
before_action :signed_in_user
def show
#trip = Trip.find(params[:id])
end
def index
#trips = current_user.Trip.all
end
def new
#trip = Trip.new
end
def create
# #trip = Trip.new(trip_params)
#trip = current_user.trips.build(trip_params)
if #trip.save
flash[:success] = "Your trip was successfully published!"
redirect_to #trip
else
render 'new'
end
end
def edit
end
def update
if #trip.update_attributes(trip_params)
flash[:success] = "Trip was updated"
redirect_to #trip
else
render 'edit'
end
end
def destroy
Trip.find(params[:id]).destroy
flash[:success] = "trip was deleted. Thank you"
redirect_to #user #root_url
end
private
def trip_params
params.require(:trip).permit(:title, :description)
end
end
class CitiesController < ApplicationController
before_action :signed_in_user
def create
#city = City.new(city_params)
if #city.save
# flash[:success] = ""
else
render 'new'
end
end
# def destroy
# City.find(params[:id]).destroy
# flash[:success] = “City was deleted."
# redirect_to root_url
# end
private
def city_params
params.require(:city).permit(:name, :province, :country)
end
end
class HotelsController < ApplicationController
before_action :signed_in_user
def create
#similar to city
end
def destroy
#similar to city
end
private
def hotel_params
params.require(:hotel).permit(:name, :address,
:management_contact,
:city_id)
end
end
And here’s the problem:
I want to have/add create forms within the trip one in
sample_app/app/views/trips/new.html.erb
<% provide(:title, 'New Trip') %>
<h1>New Trip</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#trip) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_field :title, placeholder: "Type in a title" %>
<%= f.text_field :description, placeholder: "Any additional info." %>
<%= f.submit "Publish", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
accepts_nested_attributes_for allows you to save attributes on associations. Although you do have associations in your models it doesn't necessarily mean that you need to use accepts_nested_attributes_for. It depends on how the code flows through your controllers.
Simple Example
For example, you would probably want to allow your users to view their trips and reviews. First you'll need a method to get the current user:
users_controller.rb
class ApplicationController < ActionController::Base
def current_user
#current_user ||= User.find(session[:user_id])
end
end
This method will be inherited by all of your controllers and allow them to get the current user. (There are many solutions out there for getting the current user, this is definitely not a good solution but it is OK for demonstration purposes).
Trips & Reviews
Now you can create some controllers for the current user to view their trips and reviews:
trips_controller.rb
class TripsController < ApplicationController
def index
#trips = current_user.trips.all
end
end
reviews_controller.rb
class ReviewsController < ApplicationController
def index
#reviews = current_user.reviews.all
end
end
Now you have some controller actions displaying the trips/reviews for the current user. I think this example demonstrates how you can create your controllers and that accepts_nested_attributes_for is not necessarily required.