What is the proper way to make an increment function work? - ruby-on-rails

I'm creating a website that let people read short stories in several chapters.
For this, I nested a chapter scaffold inside a novel scaffold, and linked (novel has_many :chapters, chapters belongs_to :novel) them both together.
However I'm trying to get the chapter's number inside the URL (instead of the id, which never decreases). Setting the chapter as is is not a problem, but I'd like to automate the chapter number without my users needing to add it themselves.
To do so, I figured that all I needed was to get how many chapter the current novel had by checking self.class.where(:novel_id => #novel).count. At this point, I have no issues, however it gets complicated when I try to increment this number, I get the error: undefined method 'anoter_one' for 0:Fixnum
Here is my "another_one" function inside my model (I tried some things)
def another_one
#number = self.class.where(:novel => #novel).count.to_i
#number.increment
end
Here is the controller
def create
#novel = Novel.find(params[:novel_id])
#chapter = Chapter.new(chapter_params)
#chapter.chapter_number.another_one
#chapter.novel = #novel
if #chapter.save
redirect_to novel_chapter_path(#novel, #chapter), notice: 'Chapter was successfully created.'
else
render :new
end
end
What am I doing wrong ?
Thank you in advance

Your calling anoter_one - which is a misspelling of another on the value of #chapter.chapter_number - not the model.
One way to solve this is by using an association callback:
class Novel
has_many :chapters, before_add: :set_chapter_number
def set_chapter_number(chapter)
if chapter.chapter_number.blank?
chapter.chapter_number = self.chapters.size + 1
end
end
end
In order for the callback to be called properly you want to build the associated items off the parent:
def new
#novel = Novel.find(params[:novel_id])
#chapter = #novel.chapters.new
end
def create
#novel = Novel.find(params[:novel_id])
#chapter = #novel.chapters.new(chapter_params)
if #chapter.save
redirect_to [#novel, #chapter], notice: 'Chapter was successfully created.'
else
render :new
end
end

Related

How to Refactor Controller Code To Model Code

I've been using rails for a while, and my controller code starts to get out of control (no pun intended.) I've heard you want skinny controllers and fat models, and for me this hasn't come naturally in my rails progression.
I'll post one model of my rails app.
line_item #create
def create
#cart = current_cart
#product is built from base_product, after finding associated product
#base_product_id = params[:line_item][:base_product_id]
get_product_from_base_product
#line_item = #cart.line_items.build(
:product_id => #product_id,
:order_id => nil,
:weight => params[:line_item][:weight],
:quantity => params[:line_item][:quantity]
)
## Does a line item with the same product_id already exist in cart?
if #line_item.exists_in_collect?(current_cart.line_items)
#if so, change quantity, check if there's enough stock
if current_cart.where_line_item_with(#product_id).update_quantity(#line_item.quantity) == true
#line_item.destroy
redirect_to base_products_path
flash[:success] = "Detected Producted In Cart, Added #{#line_item.quantity} More to Quantity"
else
redirect_to cart_path
flash[:failure] = "Cannot Add To Cart, We Only Have #{#line_item.product.stock_qty} In Stock"
end
else
if #line_item.stock_check == true
if #line_item.save
respond_to do |format|
format.html { redirect_to base_products_path,
:notice => "(#{#line_item.quantity}) #{#line_item.product.base_product.title} Added to Cart." }
format.xml { render :xml => #line_item,
:status => :created, :location => #line_item }
end
else
format.xml { render :xml => #line_item.errors,
:status => :unprocessable_entity }
end
else
redirect_to base_products_path
if #line_item.product.stock_qty > 0
flash[:failure] = "Sorry! We Only Have #{#line_item.product.stock_qty} In Stock"
else
flash[:failure] = "Sorry! That Item Is Out Stock"
end
end
end
end
controller methods:
private
def get_product_from_base_product
#product_id = Product.where(:base_product_id => #base_product_id).where(:size_id => params[:line_item][:size]).first.id
end
LineItem model
class LineItem < ActiveRecord::Base
belongs_to :product
belongs_to :cart
after_create :set_order_weight#, :set_package_dimentions
after_update :set_order_weight#, :set_package_dimentions
#max capactiy here
def have_enough_product?
if self.product.stock_qty > self.quantity
return true
else
self.quantity = self.quantity_was
self.save
return false
end
end
def over_order_cap?
if self.quantity > 50
return true
end
end
def update_quantity(qty)
if self.have_enough_product? == true
self.quantity += qty
self.save
end
end
def stock_check
if self.product.stock_qty > self.quantity == true
return true
else
return false
end
end
def exists_in_collect?(items)
items.each do |item|
return true if self.product_id == item.product_id
end
return false
end
As you can see, this is somewhat large controller. Is this normal? All the logic is happening there. A user may already have this item in the cart, this item may not be in stock, this item has a capacity and the user is trying to order more than that. What's more, I
I know this isn't a typical stack question. Nothing is broken, but I just don't feel good about my code.
I don't want to make a 'homework' question, but I'd appreciate suggestions on how to refactor controller code with models. I'd appreciate references and suggestions.
Thanks!
update added cart.rb
class Cart < ActiveRecord::Base
has_many :line_items#, dependent: :destroy
has_one :order
def where_line_item_with(prod_id)
line_items.where(:product_id => prod_id).first
end
def sub_total
self.line_items.to_a.sum { |item| item.total_price }
end
end
I'll answer not because I'm an expert, but because I just recently went through this myself. For me, the realization came when I began putting my app under a test suite. I was having a really tough time testing my controllers actions because they did so many different things.
Here are a couple of reasons why I think you should move and separate the logic of your controller into model methods:
Reuse of code. I found that instead of writing the same code 3 or 4 different times in different controllers, I could write it once (and test it once!) in the model and call those methods in my controllers.
Testing. Rather than testing the same logic in 3 or 4 controller actions, you can test it once in the model spec (easier to test there as well). Not only does it save you time writing tests, but it removes the possibility that you implement things differently across controllers for what should be the same logic.
Readability. I'll be honest, I didn't read through your entire create action because it wasn't very readable. I had a couple similar actions, and I hated trying to debug them because it was hard to follow what was happening. Moving that logic to the model (and separating it into smaller, more specific methods) allows you to focus on the things a controller should be doing (auth, directing, handling params etc.).
So, how to refactor it? I read something in a SO question (can't find it now) that essentially said: if the logic deals with the way resources are associated, it should be in the model. I think that's a pretty good general rule. I would start there. If you don't have a test suite, I would add that simultaneously.

Dealing with a unique error

So I have got a database called Awards.
Users are able to 'award' a recipe but they can only do this once. The award database consists of recipe_id and user_id. I have made both of these columns unique so it wont allow you to award the recipe more than once. This works fine and if you attempt to award the recipe a second time I get this error:
columns user_id, recipe_id are not unique
Is there some code I can add into th create action to check for this error and then render a flash error message such as "already awarded recipe" instead of showing the error console?
this is my create method:
def create
#award = current_user.awards.build(award_params)
if #award.save
flash[:success] = "Award Given!"
redirect_to recipe_path(params[:recipe_id])
else
render 'static_pages/home'
end
end
Thanks,
Mike
There is a whole section of rails called validations that you're hitting on. The documentation is here: link. To get you basically set up, you could:
# award.rb
class Award < ActiveRecord::Base
validates_uniqueness_of :user_id, :recipe_id
end
# awards_controller.rb
def create
#award = current_user.awards.build(award_params)
if #award.save
flash[:success] = 'Award Given!'
redirect_to recipe_path(params[:recipe_id])
else
flash[:error] = 'There was an error awarding this award!'
render 'static_pages/home'
end
end

adding shallow nested associations controller before action

I'm use a short form of shallow routing:
resources :officers, except: :new
resources :members do
resources :officers, only: :new
end
This is a member has_many officers : officers belong_to member association. Members can fill many officer roles over the years, the reason for has_many.
I've had problems dealing with this for years and decided to ask a question on the preferred Rails way of handling the creation of an associated has_many record.
On the Members show page I have a link:
= link_to 'New Officer', new_member_officer_path(#member)
There is no new_officer_path link in the application, but I wanted to trap someone putting in officers/new in the url. In the Officers _form partial, I set a hidden field member_id to #member.id if its a new record (id.nil?)
My first failed attempt:
def new
set_member
#officer = Officer.new(member_id: #member.id)
end
def set_member
if params[:member_id]
#member = Active.find(params[:member_id])
else
redirect_to members_path, alert: "Officers must be created from the Members Show view"
end
end
This failed, I guess because you can't redirect from a called method from an action.
I then tried:
before_action :set_member, only: :new
def new
#officer = Officer.new(member_id: #member.id)
end
def set_member
if params[:member_id]
#member = Active.find(params[:member_id])
else
redirect_to members_path, alert: "Officers must be created from the Members Show view"
end
end
This worked in a valid members/xx/officers/new url call but Failed in an on officers/new url. Since there is not a new route, it went to the show action and failed try to find :id new
Removing the only: :new condition on resources officers allow that approach to work, but I have an unneeded route.
Moving stuff around in my first attempt:
def new
set_member
if #member
#officer = Officer.new(member_id: #member.id)
else
redirect_to members_path, alert: "Officers must be created from the Members Show view"
end
end
def set_member
if params[:member_id]
#member = Active.find(params[:member_id])
end
end
Works, but for some reason does not feel right. I almost like leaving the new route in and the before_action approach.
Again, just looking for the standard approach. Also, is setting the foreign id in a hidden field the standard approach?
Personally I would write it like this:
def new
#member = Active.find(params[:member_id]) if params[:member_id].present?
if #member.present?
#officer = #member.officers.build
else
redirect_to members_path, alert: "Officers must be created from the Members Show view"
end
end
Using present? avoids any potential issues with the way Ruby handles nil as false-y. Not a huge concern, but I think it's best practice to return booleans instead of memorizing Ruby's interpretation of objects to boolean.
Using build automatically sets the member_id. And I didn't feel that set_member needed to be it's own method. However, if the logic in that method gets more complicated, I might revert back to using set_member.

Getting "undefined method for Nil Class" error when calling the reciprocal model method

I currently have two models School and Course where School has_many courses, and Course belongs_to school. Additionally, School and Course are nested resources, where School is the parent resource, and Course the child.
I have created several test records in the Rails Console so that a query such as when the child calls upon the parent Course.first.school successfully executes and returns all the relevant information of the school Course.first is associated with.
However, when put into a controller function, I would instead get an error "undefined method `school' for nil:NilClass" for the following line:
redirect_to school_course_path(#course.school, #course)
.. as if the .school part wasn't recognized (where as it was in the console). Why is this the case, and how do I get past this error? Thanks!
Edit - as suggested, it could be that my #course instance variable isn't passed from method to method in the controller. I have attempted at passing them through via a private method, but its still giving me the same error. Here is my code (background: the model Question belongs_to Course, with Course having many questions. Course isn't part of the nested routes)
class QuestionsController < ApplicationController
def new
#course = Course.find(params[:course]) #confirmed working
self.current_course = #course #I attempt to set current_course, a private method
#question = Question.new
end
def create
#question = Question.new(params[:question]) #also works, in rails console all the questions confirms to have rails id
if #question.save
redirect_to school_course_path(current_course.school, current_course) #source of my frustrations - continues to returns same error message
else
render 'new'
end
end
private
def current_course=(course)
#current_school = course
end
def current_course
#current_course
end
end
Should work if your relationships are set up the way I think they are:
def create
#question = Question.new(params[:question])
#course = #question.course
if #question.save
redirect_to school_course_path(#course.school, #course)
else
render 'new'
end
end
Make sure you have something like this in your create action:
#course = Course.new(params[:course])
your code is okay, it seems there is problem in your redirect.. redirect it to root_path and check whether it is working??

render a child & parent objects to a specified template

I have two models, a Charity model and a Milestone model. A charity has_many milestones.
To keep the interface a bit simpler, I'm putting the small milestone form on the show view of the Charity controller. This is fine, but when the charity model fails to save, how do i go back and render those objects with the show action so I get their invalid state?
Here's my create action on the milestones controller. Presently I'm getting a model_name error for nil, meaning that the instance variables are likely not set.
def create
#charity = Charity.find(params[:charity_id])
#charity.milestones.build(params[:milestone])
if #charity.save
redirect_to #charity, notice: "Milestone added"
else
render 'charities/show'
end
end
I know you can normally do just render action :new, but since this is across controllers, it does not work. Ideas?
Forgot to set the #milestone instance variable. Silly mistake. Hope this helps someone down the road.
def create
#charity = Charity.find(params[:charity_id])
#milestone = #charity.milestones.build(params[:milestone])
if #charity.save
redirect_to #charity, notice: "Milestone added"
else
render 'charities/show'
end
end

Resources