Rails first or initialize not working - ruby-on-rails

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.

Related

Retrieving data from polymorphic tables in rails

I have some polymorphic relationships set up and are working well for the primary purpose. That is for a User to be able to Comment on both Articles and Coffeeshops.
However I'm struggling with being able to display the users list of comments on their profile page. In the future I also want the user to be able to 'favourite' and 'want to go to' different coffeeshops which I would also want to show up on their profile page. I'm hoping once I get the logic for display current comments, the rest will be a breeze ;)
So what I have:
Models
class User < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :user
belongs_to :commentable, polymorphic: true
end
class Coffeeshop < ApplicationRecord
has_many :comments, as: :commentable
end
class Article < ApplicationRecord
has_many :comments, as: :commentable
end
Comment Controller
class CommentsController < ApplicationController
before_action :load_commentable
before_action :authenticate_user!
before_action :comment_auth, only: [:edit, :update, :destroy]
def index
#comments = #commentable.comments
end
def new
#comment = #commentable.comments.new
end
def create
#comment = #commentable.comments.new(allowed_params)
#comment.user_id=current_user.id if current_user
if #comment.save
redirect_to #commentable, notice: "Comment created."
else
render :new
end
end
def update
#comment = Comment.find(params[:id])
if #comment.update(comment_params)
redirect_to #commentable
else
render 'edit'
end
end
def destroy
#comment = Comment.find(params[:id])
#commentable = #comment.commentable
if #comment.destroy
flash[:success] = "Comment Destroyed!"
redirect_to :back
end
end
private
def allowed_params
params.require(:comment).permit(:name, :body)
end
def load_commentable
resource, id = request.path.split('/')[1,2]
#commentable = resource.singularize.classify.constantize.find(id)
end
def comment_params
params.require(:comment).permit(:body).merge(user_id: current_user.id)
end
Profile Controller
class ProfileController < ApplicationController
before_action :authenticate_user!
def index
end
def show
#user = User.find.current_user(params[:id])
#comments = #commentable.comments
end
In views/profile/show.html.erb. I was trying to do:
<h3>Your Latest Comment</h3>
<%=#comment.user.body%>
But this clearly isn't right as I get Couldn't find User without an ID. From ProfileController#show
update
If I change ProfileController to
before_action :authenticate_user!
def index
#user = User.find.current_user(params[:user_id])
end
def show
#comments = #commentable.comments
end
I get an error for undefined comments.
ok first return this to show moving it to index is not solving a problem the index is not called so write show like this.
def show
#user = current_user #you get instance of a user that is logged in
#comments = #user.comments
end
I do not know if you have user_id in your comment migration but if you do not have you must write
class User < ApplicationRecord
has_many :comments, as: :commentable
end
view
<h3>Your Latest Comment</h3>
<%=#comments.try(&:last).try(&:body)%>

Can't add Products back to Products model after deleting it from Cart, Ruby on rails

In an E commerce Rails App I'm building products that is deleted from the ShoppingCart are not added back to the production model after deletion.
When I add Products to the Cart the App is using this controller below to decrease the number of products from the Product model( see the create method)
controllers/product_item_controller.rb
class ProductItemsController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:create]
before_action :set_product_item, only: [:show, :destroy]
def create
#product = Product.find(params[:product_id])
#product_item = #cart.add_product(#product.id)
if #product_item.save
redirect_to root_url, notice:'Product added to Cart'
product = Product.find params[:product_id]
product.update_columns(stock_quantity: product.stock_quantity - 1)
else
render :new
end
end
private
def set_product_item
#product_item = ProductItem.find(params[:id])
end
def product_item_params
params.require(:product_item).permit(:product_id)
end
end
That is woking fine.
But when I delete the Cart it gets deleted but the products are not added to the products model. And I also get this messages : Invalid Cart
this is the carts_controller.rb
class CartsController < ApplicationController
before_action :set_cart, only: [:show, :destroy]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
def new
#cart = Cart.new
end
def show
#images = ["1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"]
#random_no = rand(5)
#random_image = #images[#random_no]
end
def destroy
#cart.destroy if #cart.id == session[:cart_id]
session[:cart_id] = nil
product = Product.find params[:product_id]
product.update_columns(stock_quantity: product.stock_quantity + 1)
redirect_to root_url, notice: 'Your Cart is Empty'
end
def remove
cart = session['cart']
item = cart['items'].find { |item| item['product_id'] == params[:id] }
product = Product.find(item['product_id'])
product.update_columns(stock_quantity: product.stock_quantity + 1)
if item
cart['items'].delete item
end
redirect_to cart_path
end
private
def set_cart
#cart = Cart.find(params[:id])
end
def cart_params
params[:cart]
end
def invalid_cart
logger_error = 'You are trying to access invalid cart'
redirect_to root_url, notice: 'Invalid Cart'
end
end
I Can't see what is wrong with this code and why the products are not added to the product.rb after being deleted from the Cart.
Am I missing something here? Could someone advise me here?
Below are other relevant models and controllers
products_controller.rb
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
def show
end
def search
#product = Product.search(params[:query]).order("created_at DESC")
#categories = Category.joins(:products).where(:products => {:id => #product.map{|x| x.id }}).distinct
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
#product = Product.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:title, :description, :price_usd, :price_isl, :image, :category_id, :stock_quantity, :label_id, :query)
end
end
Cart.rbmodel
class Cart < ActiveRecord::Base
has_many :product_items, dependent: :destroy
def add_product(product_id)
current_item = product_items.find_by(product_id: product_id)
if current_item
current_item.quantity += 1
else
current_item = product_items.build(product_id: product_id)
end
current_item
end
def total_price_usd
product_items.to_a.sum{|item| item.total_price_usd}
end
def total_price_isl
product_items.to_a.sum{|item| item.total_price_isl}
end
end
product.rbmodel
Class Product < ActiveRecord::Base
belongs_to :category
belongs_to :label
has_many :product_item, :dependent => :destroy
#before_destroy :ensure_not_product_item
validates :title, :description, presence: true
validates :price_usd, :price_isl, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true
has_attached_file :image, styles: { medium: "500x500#", thumb: "100x100#" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
#def ensure_not_product_item
# if product_item.empty?
# return true
# else
# errors.add(:base, 'You have Product Items')
# return false
# end
#end
def self.search(query)
where("title LIKE ? OR description LIKE ?", "%#{query}%", "%#{query}%")
end
end
You are rescuing from ActiveRecord::RecordNotFound
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
But you're probably rescuing inappropriately... from the Product.find... in the destroy method. I'm not sure why you would expect the product_id to be in params.
Your code...
def destroy
#cart.destroy if #cart.id == session[:cart_id]
session[:cart_id] = nil
product = Product.find params[:product_id]
product.update_columns(stock_quantity: product.stock_quantity + 1)
redirect_to root_url, notice: 'Your Cart is Empty'
end
A better alternative might be...
def destroy
if #card.id == session[:cart_id]
#cart.product_items each do |product_item|
product_item.product.update_columns(stock_quantity: product_item.product.stock_quantity + 1)
end
#cart.destroy
end
end
However this might better be done as a before_destroy action for product_item model, so that destroying a product_item will automatically increment the stock total.
I'm not going to give a line by line solution as there are quite a few points about this application that not quite right and require a bit of rethinking. Lets look at how a shopping cart commonly is done.
The models:
class User < ApplicationRecord
has_many :orders
has_many :products, through: :orders
def current_order
orders.find_or_create_by(status: :open)
end
end
class Order < ApplicationRecord
enum status: [:in_cart, :processing, :shipped]
belongs_to :user
has_many :line_items
has_many :products, through: :line_items
end
# The join model between a Order and Product
# The name line item comes from the lines on a order form.
class LineItem < ApplicationRecord
belongs_to :order
belongs_to :product
end
class Product < ApplicationRecord
has_many :line_items
has_many :orders, through: :line_items
end
The naming here is not a mistake or sloppy copy pasting. A cart is only a concept in web app which exists as a "user aid" in creating an order.
The join between a Order and Product is commonly called a line-item. Note that we use has_many though: so that we can query:
User.find(1).order
Product.find(1).orders
Order.find(1).products
The Controllers
When building something as complicated as a checkout you will want to pay attention to the Single Responsibility Principle and KISS. Having many classes is not a bad thing. Having huge tangled controllers that do far too much is.
So for example create a controller that has adding and removing items from the cart as its sole responsibility.
# routes.rb
resource :cart
resources :line_items,
only: [:create, :destroy, :update] do
collection do
delete :clear
end
end
end
# app/controllers/line_items.rb
class LineItemsController < ApplicationController
before_action :set_cart
before_action :set_item
rescue_from Orders::NotOpenError, -> { redirect_to #order, error: 'Order is locked and cannot be edited' }
# Add an item to cart
# POST /cart/line_items
def create
#cart.product_items.create(create_params)
# ...
end
# Remove an item from cart
# DESTROY /cart/line_items/:id
def destroy
#item.destroy
if #item.destroyed?
redirect_to cart_path, success: 'Item removed.'
else
redirect_to cart_path, alert: 'Could not remove item.'
end
end
# Remove all items from cart
# DESTROY /cart/line_items
def clear
#order.line_items.destroy_all
if #order.items.count.zero?
redirect_to cart_path, success: 'All items destroyed'
else
redirect_to cart_path, alert: 'Could not remove all items.'
end
end
# Update a line in the order
# PATCH /cart/line_items/:id
def update
#line_item.update(update_params)
end
private
def set_order
#order = current_user.current_order
# Ensure that order is not processed in some way
raise Orders::NotOpenError unless #order.open?
end
def set_line_item
#line_item = #order.line_items.find(params[:id])
end
def create_params
params.require(:line_item).permit(:product_id, :quantity)
end
def update_params
params.require(:line_item).permit(:quantity)
end
end
Notice how nicely the path for route each clearly tells us what it does and how we can write a description of the controller in a single line without using the word and.
In addition to this you will want a ProductsController, CartController, OrderController, PaymentsController etc. each of should do a single job - and do it well.
Don't do it all in your controllers!
When we add a line item to a order the available stock of the product should of course decrease. This is a clear cut example of business logic.
In MVC business logic belongs in the model layer.
A user adding a item to the cart should only create a reservation. The actual inventory of a product should only be altered when the order is processed or ships:
# No callbacks needed!
class Product < ApplicationRecord
has_many :line_items
has_many :orders, through: :line_items
def reservations
line_items.joins(:order)
.where
.not(line_items: {
order: Order.statuses[:shipped]
})
.sum(:quantity)
end
def availibity
stock - reservations
end
end
You've got
before_action :set_cart, only: [:show, :destroy]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
As soon as the CartsController#destroy method is invoked the private method set_cart is called. What it tries to do is to initialize an instance variable #cart = Cart.find(params[:id]).
The first line of your #destroy method is #cart.destroy if #cart.id == session[:cart_id]. Isn't the #cart = Cart.find(params[:id]) a problem here? What is the value of params[:id]? I guess it's not the same as session[:cart_id] and might probably be a nil or some Intreger value by which the DB cannot find a Cart record, hence the error.
Edit 1:
The same applies to the product = Product.find params[:product_id] as Steve mentioned in his answer.
Max posted a very informative report on how it should be done properly. If you have the time stick to his answer and try to redesign your app in accordance to his suggestion.

Nested Comments with nested answers: undefined method `answers' for nil:NilClass | Rails

I am trying to implement nested answers into comments, which are nested into auctions.
There is a auctions.rb model, which:
has_many :comments, dependent: :destroy
has_many :answers, :through => :comments
a comments.rb model, which:
belongs_to :auction
has_many :answers, dependent: :destroy
a answers.rb model, which:
belongs_to :comment
the answers_controller inherits from the comments_controller:
class AnswersController < CommentsController
before_action :all_answers, only: [:index, :create, :update, :destroy]
before_action :set_answer, only: [:edit, :update, :destroy]
respond_to :html, :js
# New Answer (Form)
def new
#answer = Answer.new
#comments.answers.build
end
# Create Answer
def create
#answer = #comment.answers.build(answer_params)
#answer.user_id = current_user.id
#answer.save
end
# Edit Answer
def update
#answer.update!(answer_params)
end
# Delete Answer
def destroy
#answer = Comment.find(params[:id])
#comment = #answer.comment
#answer.destroy
end
private
def all_answers
#answers = #comment.answers.all
end
def set_answer
#answer = #comment.answers.find(params[:id])
end
def answer_params
params.require(:comment).permit(:body)
end
end
The Error:
NoMethodError in Auctions#show app/views/comments/_comment.html.erb
where line #20 raised: undefined method `answers' for nil:NilClass
<div class="col s12" id="answer-form" style="display:none;"></div>
</div>
<div class="row">
<div class="col s12" id="answers"><%= render #comment.answers %></div>
</div>
With <%= render #comment.answers %> I want to display all existing answers below the related comment. What am I doing wrong?
auction_controller
class AuctionsController < ApplicationController
# Index of all auctions
def index
#auctions = Auction.all
end
# Show Auction by :id
def show
#auction = Auction.find(params[:id])
# Find Seller by ID
#seller = User.find(#auction.user_id)
# Find highest bid, by finding all related bids and ordering in descending and picking the first
#highest_bid = Bid.where(auction_id: params[:id]).order("amount DESC").first
# Find product
#product = Product.find(#auction.product_id)
end
# New Auction Form
def new
#auction = Auction.new
end
# Edit Auction
def edit
#auction = Auction.find(params[:id])
end
# Create new Auction
def create
# Create new Auction
#auction = Auction.new(auction_params)
# Save Id of User (Seller)
#auction.user_id = current_user.id
# If auction was created successfully
if #auction.save
# display the created auction
redirect_to #auction, :notice => "Auction created"
else
# display Form again if unsuccessful
render 'new'
end
end
# Update existing Auction
def update
#auction = Auction.find(params[:id])
# Validation
if #auction.update(auction_params)
redirect_to #auction, :notice => "Auction updated"
else
render 'edit'
end
end
# Delete Auction
def destroy
#auction = Auction.find(params[:id])
#auction.destroy
redirect_to auctions_path, :notice => "Auction deleted"
end
private
# set required parameters for new created Auctions
def auction_params
params.require(:auction).permit(:condition, :product_name)
end
end
comments_controller
class CommentsController < ApplicationController
before_action :set_auction
before_action :all_comments, only: [:index, :create, :update, :destroy]
before_action :set_comment, only: [:edit, :update, :destroy]
respond_to :html, :js
# New Comment (Form)
def new
#comment = Comment.new
#auction.comments.build
end
# Create Comment
def create
#comment = #auction.comments.build(comment_params)
#comment.user_id = current_user.id
#comment.save
end
# Edit Comment
def update
#comment.update!(comment_params)
end
# Delete Comment
def destroy
#comment = Comment.find(params[:id])
#auction = #comment.auction
#comment.destroy
end
private
def set_auction
#auction = Auction.find(params[:auction_id])
end
def all_comments
#comments = #auction.comments.all
end
def set_comment
#comment = #auction.comments.find(params[:id])
end
def comment_params
params.require(:comment).permit(:body)
end
end
Normal Comments work. Only Comment Answers don't work.
The error happens in Auctions#show, the error clearly tells you that you are trying to call answers on a nil object. Therefore, it means #comment is nil in that view.
In fact, if you check the show action, you never fetch/assign any object to #comment.
# Show Auction by :id
def show
#auction = Auction.find(params[:id])
# Find Seller by ID
#seller = User.find(#auction.user_id)
# Find highest bid, by finding all related bids and ordering in descending and picking the first
#highest_bid = Bid.where(auction_id: params[:id]).order("amount DESC").first
# Find product
#product = Product.find(#auction.product_id)
end
In order to fix it, make sure #comment is properly assigned to a Comment instance.
There's another problem here:
def new
#answer = Answer.new
#comments.answers.build
end
You haven't got a variable called #comments, hence your form can't actually build answers off it. In fact, you're calling #comment in two other methods, where I can't even see it being declared:
def all_answers
#answers = #comment.answers.all
end
def set_answer
#answer = #comment.answers.find(params[:id])
end
The only time you've declared #commentis in the destroymethod:
def destroy
#answer = Comment.find(params[:id])
#comment = #answer.comment
#answer.destroy
end
Even then, it's weird.
Why are you calling the Comment model with the #answer variable? Surely you'd have an Answer model with comments attached by way of a has_many relationship?
I'd recommend you keeping it brain-dead simple:
#app/models/answer.rb
class Answer < ActiveRecord::Base
has_many :comments
end
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :answer
end
This means when you call your actions controller, you'll be able to apply the following:
def show
#answer = Answer.find params[:id]
#comment = #answer.comments.build
end
If you wanted to have comments act as answers, you need to keep the models separate. Use a hierchy gem, like closure tree. This way, you'll be able to keep your Answers/Comments in hierarchy order, whilst keeping the Models consistent.

saving user id in rails user-submissions-contest has many through model

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.

Method missing when calling an method associated with another class (:belongs_to relationship)

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.

Resources