How to configure the rails controller so I can have a user post a submission in no matter what contest. When they post their user id and the contest id should be automatically appended to the submission.
I know I can do:
User.first.contests.create => let the user create a contest
Contest.first.submissions.create => create a submission in a contest (not linked to a user)
User.first.submissions.create => create a submission linked to a user but not to a contest
I cannot do User.first.Contest.last.submissions.create => I want to link a submission to a contest and to a submission.
Is there an elegant way to fix this?
The submission controller looks like this:
class SubmissionsController < ApplicationController
before_action :set_submission, only: [:show, :edit, :update, :destroy]
# the current user can only edit, update or destroy if the id of the pin matches the id the user is linked with.
before_action :correct_user, only: [:edit, :update, :destroy]
# the user has to authenticate for every action except index and show.
before_action :authenticate_user!, except: [:index, :show]
respond_to :html
def index
#title = t('submissions.index.title')
#submissions = Submission.all
respond_with(#submissions)
end
def show
#title = t('submissions.show.title')
respond_with(#submission)
end
def new
#title = t('submissions.new.title')
#submission = Submission.new
respond_with(#submission)
end
def edit
#title = t('submissions.edit.title')
end
def create
#title = t('submissions.create.title')
#submission = Submission.new(submission_params)
#submission.save
respond_with(#submission)
end
def update
#title = t('submissions.update.title')
#submission.update(submission_params)
respond_with(#submission)
end
def destroy
#title = t('submissions.destroy.title')
#submission.destroy
respond_with(#submission)
end
private
def set_submission
#submission = Submission.find(params[:id])
end
def submission_params
arams.require(:submission).permit(:reps, :weight, :user_id)
end
def correct_user
#submission = current_user.submissions.find_by(id: params[:id])
redirect_to submissions_path, notice: t('submissions.controller.correct_user') if #submission.nil?
end
end
I have following models:
class Contest < ActiveRecord::Base
has_many :submissions
has_many :users, through: :submissions
class Submission < ActiveRecord::Base
belongs_to :user
belongs_to :contest
class User < ActiveRecord::Base
has_many :submissions
has_many :contests, through: :submissions
I think you're making this a bit complicated.
Submission is POSTED within Contest, Submission needs to know the user_id.
<%= simple_form_for :submission, url: contest_submissions_path(contest) do |f| %>
...
<%= f.submit 'Submit', class: "button" %>
<% end %>
And on your submissions CREATE method
class SubmissionsController < ApplicationController
def create
#contest = Contest.find(params[:contest_id])
#submission = #contest.submissions.new(submission_params)
#submissions.user = current_user
.....
end
The magic happens at #submissions.user = current_user If you are using Devise, it is easy to pass in the current_user.id ANYWHERE in the controller, as I just did in the submissions controller.
Are you able to use #submission = current_user.submissions.new(submission_params) and #contest = Contest.find(params[:contest_id]) in your SubmissionsController#create
EDIT: I've added some details on adding a reference to contest_id in the submissions table.
The best way I've found to tie related things together in Rails (and indeed, any relational database) is to add a reference in the child table to the parent's id. You can do this with a migration in Rails.
rails g migration AddContestToSubmission contest:references
And modify the migration file generated in your db/migrate/<datetime>_add_contest_to_submission to look similar to:
class AddContestToSubmission < ActiveRecord::Migration
def change
add_reference :submissions, :contest, index: true
end
end
Then go ahead and look at your submissions table in your schema.rb. You should notice something like t.integer "contest_id" You should probably also add the user_id in your migration is you want a submission to be tied to one user.
Related
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
I have a product.
I have an order.
I have a booking in between.
Whenever I make a booking from the product to the order it saves a new unique booking.
It should:
Save a new booking when it's the first made from this product.
Not make a new booking, but find and overwrite an old one, if the product has already been booked once.
If the product has already been booked on the order, but no changes are made, no database transactions are made.
def create
#order = current_order
#booking = #order.bookings.where(product_id: params[:product_id]).first_or_initialize
product = #booking.product
if #booking.new_record?
#booking.product_name = product.name
#booking.product_price = product.price
else
#booking.product_quantity = params[:product_quantity]
#booking.save
#order.sum_all_bookings
#order.save
end
Doesn't work.
Following worked:
def create
#booking = #order.bookings.find_by(product_id: params[:booking][:product_id])
if #booking
#booking.product_quantity = params[:booking][:product_quantity]
#booking.save
else
#booking = #order.bookings.new(booking_params)
#product = #booking.product
#booking.product_name = #product.name
#booking.product_price = #product.price
end
#order.save
end
Apparently I needed to grab the params, by adding [:booking] like in params[:booking][:product_id]. Anybody knows why?
You can try
#order.bookings.find_or_initialize_by(product_id: params[:product_id]).tap do |b|
# your business logic here
end
To avoid duplicates you should setup relations properly and using a database index to ensure uniqueness.
class Order
has_many :bookings
has_many :products, though: :bookings
end
class Booking
belongs_to :order
belongs_to :product
validates_uniqueness_of :order_id, scope: :product_id
end
class Product
has_many :bookings
has_many :orders, though: :bookings
end
The validation here will prevent inserting duplicates on the application level. However it is still prone to race conditions.
class AddUniquenessContstraintToBooking < ActiveRecord::Migration[5.0]
def change
add_index :bookings, [:order_id, :product_id], unique: true
end
end
However the rest of you controller logic is muddled and overcomplicated. I would distinct routes for update and create:
class BookingsController < ApplicationController
before_action :set_order, only: [:create, :index]
before_action :set_order, only: [:create, :index]
# POST /orders/:order_id/bookings
def create
#booking = #order.bookings.new(booking_params)
if #booking.save
redirect_to #order
else
render :new
end
end
# PATCH /bookings/:id
def update
if #booking.update(:booking_params)
redirect_to #order
else
render :edit
end
end
private
def set_order
#order = Order.find(params[:id])
end
def set_booking
#booking = Booking.find(params[:id])
end
def booking_params
params.require(:booking)
.permit(:product_id)
end
end
Another alternative is to use accepts_nested_attributes - but try to keep it simple.
This has been asked on SO a lot before, but I can't find anything that quite applies. What I'm trying to do is render an edit form for SettingsController in the edit view of UsersController. I'm super new to RoR, so I'm not even sure what I'm doing wrong.
This questions seems closest, but when I initialize #setting = Setting.new in the Users controller, I get a Settings form without the defaults set for new users in the migration. But if I initialize #setting = Setting.edit or Setting.update, I get an undefined method or wrong number of arguments error.
When the Setting.new form is saved, it throws this error:
undefined method for find_by_id in the SettingsController: app/controllers/settings_controller.rb:43:in `correct_user'.
When I check the database, the settings records are being correctly created when a user is created, but the settings record is not updated when the form is saved.
setting.rb:
class Setting < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
end
user.rb:
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
has_one :setting, dependent: :destroy
after_create :create_setting
end
UsersController:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update, :index, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def new
#user = User.new
end
def edit
#user = User.find(params[:id])
#setting = Setting.update
end
def update
#user = User.find(params[:id])
#setting = Setting.update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated!"
redirect_to #user
else
render 'edit'
end
end
def settings
#title = "Settings"
#setting = Setting.find_by_user_id(params[:user_id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
the SettingsController:
class SettingsController < ApplicationController
before_action :logged_in_user, only: [:create, :edit, :update, :show, :index]
before_action :correct_user, only: [:create, :edit, :update, :show, :index]
def index
#settings = Setting
end
def show
#setting = User.find(params[:id]).setting
end
def new
#setting = Setting.new
end
def edit
#setting = Setting.find(params[:id])
end
def create
#setting = current_user.settings.build(setting_params)
#setting.save
end
def update
#setting = Setting.find(params[:id])
if #setting.update_attributes(post_params)
flash[:success] = "Settings updated!"
redirect_to request.referrer
else
render 'edit'
end
end
private
def setting_params
params.require(:setting).permit(:reading_theme)
end
def correct_user
#setting = current_user.setting.find_by_id(params[:id]) ##the line which throws an error when the form is saved
redirect_to root_url if #setting.nil?
end
end
The form partial:
<%= form_for(#setting) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= radio_button_tag(:reading_theme, "flatly") %>
<%= label_tag(:reading_theme_flatly, "Light (Default)") %>
</div>
<div class="field">
<%= radio_button_tag(:reading_theme, "darkly") %>
<%= label_tag(:reading_theme_darkly, "Dark") %>
</div>
<%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
routes.rb:
resources :users do
member do
get :following, :followers
end
end
resources :settings, only: [:new, :create, :edit, :update]
...
ETA: the settings migration:
class CreateSettings < ActiveRecord::Migration
def change
create_table :settings do |t|
t.string :reading_theme, default: => "flatly"
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
How do I get the proper defaults so that my form can be saved correctly?
Any defaults that you include for fields in the migration will be "unknown" to the model class (Setting) in Ruby. Ruby (or rather Rails ActiveRecord) does not read the default values from the table definition when creating a model object. This can lead to a dual personality problem like you're seeing here.
What you have to do is to add the relevant defaults into the Ruby code, where appropriate. For example, in your Settings controller, you can make these changes:
def new
#setting = Setting.new
# Set any defaults that will be visible to the user on the form
#setting.reading_theme = "flatly"
# The form will allow the user to choose their own values, based on the defaults
end
def create
#setting = current_user.settings.build(setting_params)
# Set any defaults that will NOT be visible to the user
#setting.save
end
This gives you the ability to distinguish between default values that are visible to the user and defaults that are not.
Note that you also have the option of establishing defaults when you create the model object, but this may be more complicated in some situations, and seems to be far less common in practical use. There's an SO answer for that in How to initialize an ActiveRecord with values in Rails?, in case this better suits your needs.
Not can use find_by_id in has_one relationship
#setting = current_user.setting.find_by_id(params[:id])
Just #setting = current_user.setting
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'
We've attempted a has_many, belongs_to relationship: we've created a class - ArticleCategory - that belongs to Article. Articles have many article_categories. ArticleCategory has one attribute - sport:string
We are unable to call Article.last.article_categories as it returns this error message:
NoMethodError: undefined method
Here is our relevant code:
ArticleCategories Controller
class ArticleCategoriesController < ApplicationController
before_action :set_article_category, only: [:show, :edit, :update, :destroy]
def index
#article_categories = ArticleCategory.all
respond_with(#article_categories)
end
def show
respond_with(#article_category)
end
def new
#article_category = ArticleCategory.new
respond_with(#article_category)
end
def edit
end
def create
#article = Article.find(params[:article_id])
#article_category = #article.article_categories.build(article_category_params)
# #article_category = ArticleCategory.new(article_category_params)
#article_category.save
respond_with(#article_category)
end
def update
#article_category.update(article_category_params)
respond_with(#article_category)
end
def destroy
#article_category.destroy
respond_with(#article_category)
end
private
def set_article_category
#article_category = ArticleCategory.find(params[:id])
end
def article_category_params
params.require(:article_category).permit(:sport)
end
end
ArticleCategories Model
class ArticleCategory < ActiveRecord::Base
belongs_to :article
end
Articles Controller
class ArticlesController < ApplicationController
load_and_authorize_resource
skip_authorize_resource :only => [:index, :show]
def new
#article = Article.new
end
def create
#article = Article.new(article_params)
# authorize! :create, #article
if #article.save
#send email to referral email
all_users = User.all
all_users.each do |user|
ArticleMailer.article_confirmation(user,#article).deliver
end
redirect_to #article
else
render 'new'
end
end
def show
#article = Article.find(params[:id])
end
def index
#articles = Article.all.reverse
end
def edit
#article = Article.find(params[:id])
end
def update
#article = Article.find(params[:id])
if #article.update(article_params)
redirect_to #article
else
render 'edit'
end
end
def destroy
#article = Article.find(params[:id])
#article.destroy
redirect_to articles_path
end
private
def article_params
params.require(:article).permit(:title, :text, :date, :kredit, article_categories_attributes: [:id, :sport])
end
end
Articles Model
class Article < ActiveRecord::Base
has_many :comments, dependent: :destroy
has_many :article_categories, dependent: :destroy
accepts_nested_attributes_for :article_categories, :allow_destroy => true
validates :title, presence: true,
length: { minimum: 5 }
end
We can't figure out why we can't call this method on Article.last
Since my comment helped, I'll add this as an answer so we can close this off
If you had the console open when you made changes to the model, those changes aren't reflected yet until you either exit and re-enter the console, or type reload!. Either of these will cause the console to reload all of your classes. In short, your classes remain in the state they were in when you first loaded the console, so its something to keep in mind when you're developing and playing around in the console simultaneously.
Regarding your question from the comments:
When we call with an instance of the article - #article.article_categories - we get this <ArticleCategory::ActiveRecord_Associations_CollectionProxy:0x007fc46acc5db8>
That's correct, you get back an association object. Most of the time you don't need to worry about this, as invoking some array methods such as .each and such will give you the concrete objects.
The collection proxy object, however, lets you perform other active record method calls to further filter the article_categories list if you like. You can do stuff like this, for example:
article.last.article_categories.where(sport: "curling")
and the article_categories list will be limited to those that match the .where clause filter. You can verify this in the console or rails log by looking at the generated SQL query.
Hope that helps.