render :new is not working anymore when I'm using services - ruby

I'm trying to learn about services in Rails in order to avoid fat controllers.
Here a tried a simple product review creation
class ProductReviewsController < ApplicationController
before_action :set_product, only: :create
def new
#product_review = ProductReview.new
#product = Product.find(params[:product_id]) end
def create
if AddReviewService.call(product_review_params, #product)
redirect_to product_path(#product)
else
render :new
end
end
private
def set_product
#product = Product.find(params[:product_id]) end
def product_review_params
params.require(:product_review).permit(:content, :rating) end end
The thing is that in the case of wrong parameters for the review the render :new generates the following error :
screenshot of error received
Showing /home/miklw/code/michaelwautier/multitenant_app/app/views/product_reviews/new.html.erb where line #3 raised:
undefined method 'model_name' for nil:NilClass
Extracted source (around line #3):
1 <h1>New review for <%= #product.name %></h1>
2
3 <%= simple_form_for [#product, #product_review] do |f| %>
4 <%= f.input :content %>
5 <%= f.input :rating %>
6 <%= f.button :submit %>
Rails.root: /home/miklw/code/michaelwautier/multitenant_app
Application Trace | Framework Trace | Full Trace
app/views/product_reviews/new.html.erb:3:in `_app_views_product_reviews_new_html_erb__802305224391576972_70155240081100'
app/controllers/product_reviews_controller.rb:14:in `create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"Z7YeYvXKKr7uYhANlevZ4H8U00p9givKzXzfue4pNFk0jE2DtTNY7Eacqati+V8IihSofLc2WPa4ZBzR2o0v5w==",
"product_review"=>{"content"=>"te", "rating"=>"9"},
"commit"=>"Create Product review",
"product_id"=>"4"}
Toggle session dump
Toggle env dump
Response
Headers:
None
In the error page console, if I type #product, I get the expected product object, but #product_review is nil.
BUT, if I use the regular way (see below), the form gets re-render as it should, with the notice message of the form
class ProductReviewsController < ApplicationController
before_action :set_product, only: :create
def new
#product_review = ProductReview.new
#product = Product.find(params[:product_id])
end
def create
#product_review = ProductReview.new(product_review_params)
#product_review.product = #product
if #product_review.save
redirect_to product_path(#product)
else
render :new
end
end
private
def set_product
#product = Product.find(params[:product_id])
end
def product_review_params
params.require(:product_review).permit(:content, :rating)
end
end
Any idea what could cause this issue ?
EDIT : Here is the service I call :
class AddReviewService < ApplicationService
attr_reader :content, :rating
def initialize(params, product)
#content = params[:content]
#rating = params[:rating]
#product = product
end
def call
review = ProductReview.new(content: #content, rating: #rating)
review.product = #product
return true if review.save
return false
end
end
EDIT 2 : returning the review when saved
def call
review = ProductReview.new(content: #content, rating: #rating)
review.product = #product
return review if review.save
return false
end

Related

Get value out of static rails select_tag

I have just started out programming with ruby on rails. I really like it, but sometimes it's really complicated. What I am trying to do is to get the selected value out of the select_tag and pass it to the Model where I will multiply the value to another one (that comes from an from_for textfield).
The problem is I wasn't able to figure out how to get the value from the View to the Controller and then to the Model.
Here is my code:
View:
<%= label_tag 'Remind' %>
<%= f.number_field :remind %>
<%= select_tag :select_conv, options_for_select([['Day', 1], ['Week', 7], ['Month', 30]]) %>
Controller:
def create
add = Item.new(item_params)
if add.save
flash[:notice] = ''
redirect_to items_path
else
redirect_to new_item_path
flash[:error] = ''
end
private
def item_params
params.require(:item).permit(:itemname, :amount, :bbf, :remind)
end
end
Model:
def convert_to_d
convert = self.remind * self.v_convertor
self.assign_attributes(remind: convert)
end
Thank you in advance
You have to do some changes:
View:
<%= label_tag 'Remind' %>
<%= f.number_field :remind %>
<%= select_tag :select_conv, options_for_select([['Day', 1], ['Week', 7], ['Month', 30]]) %>
From the View, it will return a hash with the values of each user's input. So, for this example, it will return:
params = { remind: user_input, select_conv: user_input }
You can catch that in your controller with the method item_params, but
you have to specify the parameters that you want in your method, so your item_params should be:
Controller:
def create
add = Item.new(item_params)
if add.save
flash[:notice] = ''
redirect_to items_path
else
redirect_to new_item_path
flash[:error] = ''
end
end
private
def item_params
params.require(:item).permit(:itemname, :amount, :bbf, :remind, :select_conv) # << update here
end
In your model, you can access the values saved in item_params with their names, as you did with self.remind, you can call it with self.select_conv.
Model:
# self.select_conv can be used now.
def convert_to_d
convert = self.remind * self.v_convertor
self.assign_attributes(remind: convert)
end
You can also use some validations in your model to guarantee integrity from the user's data. For more information about validations.

NoMethodError in Users#unsubscribe

I am working on implementing unsubscribe link to my rails mailer. Unfortunately, my code breaks with this:
NoMethodError in Users#unsubscribe - undefined method `unsubscribe_hash' for nil:NilClass
which points to /app/views/users/unsubscribe.html.erb line #3
<h4>Unsubscribe from Mysite Emails</h4>
<p>By unsubscribing, you will no longer receive email...</p>
<%= simple_form_for(#user, unsubscribe_path(id: #user.unsubscribe_hash)) do |f| %>
<%= f.hidden_field(:subscription, value: false) %>
<%= f.submit 'Unsubscribe' %>
<%= link_to 'Cancel', root_url %>
<% end %>
my user_controller is as shown below
class UsersController < ApplicationController
protect_from_forgery
def new
#user = User.new
end
def create
#user = User.new(secure_params)
if #user.save
flash[:notice] = "Thanks! You have subscribed #{#user.email} for Jobs Alert."
else
flash[:notice] = 'Error Subscribing! Kindly check your email and try again.'
end
redirect_to root_path
end
def unsubscribe
user = User.find_by_unsubscribe_hash(params[:unsubscribe_hash])
#user = User.find_by_unsubscribe_hash(user)
end
def update
#user = User.find(params[:id])
if #user.update(secure_params)
flash[:notice] = 'Subscription Cancelled'
redirect_to root_url
else
flash[:alert] = 'There was a problem'
render :unsubscribe
end
end
private
def secure_params
params.require(:user).permit(:email, :subscription)
end
end
Route.rb
resources :users, only: [:new, :create]
get 'users/:unsubscribe_hash/unsubscribe' => 'users#unsubscribe', as: :unsubscribe
patch 'users/update'
user.rb
class User < ActiveRecord::Base
before_create :add_unsubscribe_hash, :add_true_to_users_table
validates :email, :uniqueness => true
validates_presence_of :email
validates_format_of :email, :with => /\A[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}\z/i
private
def add_unsubscribe_hash
self.unsubscribe_hash = SecureRandom.hex
end
def add_true_to_users_table
self.subscription = true
end
end
unsubscribe link in the email which calls unsubscribe action
# app/views/job_notifier/send_post_email.html.erb
...
<%= link_to "Unsubscribe", unsubscribe_url(id: #unsubscribe) %>.
diagrammatic view of the error
Its seems that I am missing something, do I need to define something in my users_controller? I have never in my life being able to solve NoMethodError or I don't understand what it's all about.
You get that error because #user (set in UsersController#unsubscribe) is nil. That is what the "for nil:NilClass" in undefined methodunsubscribe_hash' for nil:NilClass` is referring to.
This method doesn't seem correct:
def unsubscribe
user = User.find_by_unsubscribe_hash(params[:unsubscribe_hash])
#user = User.find_by_unsubscribe_hash(user)
end
You are looking up a user by unsubscribe_hash and assigning it to user, and then looking up user by unsubscribe_hash again but passing in user as the value to find_by_unsubscribe_hash.
I believe something like this is more as intended:
def unsubscribe
#user = User.find_by_unsubscribe_hash(params[:unsubscribe_hash])
end
Whenever you see any error messages in ruby of the format:
undefined method 'method_name' for nil:NilClass
you are being told you are trying to call something on a nil object and so the focus of your attention should be on why is that object nil. You are also told in your log where the error occurs - in your case it refers to the line, which refers to #user in #user.unsubscribe_hash in your form declaration.
So #user is nil and in this case it's nil because your controller responsible for rendering the form isn't setting #user:
user = User.find_by_unsubscribe_hash(params[:unsubscribe_hash])
#user = User.find_by_unsubscribe_hash(user)
Now quite why you are attempting to find the user and then pass that user into the second line to find #user is beyond me, but anyway the real issue is that you have no user that matches params[:unsubscribe_hash]
So the issue is related to whatever is invoking your unsubscribe action ... you have neglected to add that to your question so I cannot help with that but that is where your focus start.

Only Last Parameter being passed into Object

My app is supposed to create an image object for each image selected in the input field of my form. It–the create action– iterates over the :picture param and for each entry, it is supposed to create a new image object. However, it seems to only create an image object for the last image selected in the input box. Why is this not functioning properly?
Controller
class Admin::ImagesController < ApplicationController
respond_to :html, :json
#before_filter :split_hash, :only => [ :create, :update ]
def index
#album = Album.find(params[:album_id])
#images = #album.images.all
end
def new
#album = Album.find(params[:album_id])
#image = #album.images.new
end
def create
params[:image][:picture].each do |image|
#album = Album.find(params[:album_id])
#params = {}
#params['picture'] = image
#image = #album.images.build(#params)
end
if #image.save
flash[:notice] = "Successfully added image!"
redirect_to [:admin, #album, :images]
else
render "new"
flash[:notice] = "Did not successfully add image :("
end
end
def show
#album = Album.find(params[:album_id])
#image = #album.images.find(params[:id])
end
def edit
#album = Album.find(params[:album_id])
#image = #album.images.find(params[:id])
end
def update
#album = Album.find(params[:album_id])
#image = #album.images.find(params[:id])
if #image.update_attributes(params[:image])
flash[:notice] = "Successfully updated Image"
redirect_to [:admin, #album, :images]
else
render "edit"
end
end
def destroy
#album = Album.find(params[:album_id])
#image = #album.images.find(params[:id])
#image.destroy
#albumid = #album.id
#id = #image.id
FileUtils.remove_dir("#{Rails.root}/public/uploads/image/picture/#{#albumid}/#{#id}", :force => true)
redirect_to admin_album_images_path(#album)
end
# def split_hash
# #album = Album.find(params[:album_id])
# #image = #album.images
# array_of_pictures = params[:image][:picture]
# array_of_pictures.each do |pic|
# size = array_of_pictures.size.to_i
# size.times {#image.build(params[:image], :picture => pic)}
# #image.save
# end
# end
end
View Form
<%= form_for([:admin, :album, #image], :html => {:multipart => true}) do |f| %>
<%= f.hidden_field :album_id, :value => #album.id %>
<%= f.file_field :picture, :multiple => true %>
<%= f.submit "Submit" %>
<%end%>
Request Params on Submit
{"image"=>{"picture"=>[#<ActionDispatch::Http::UploadedFile:0x10c986d88 #tempfile=#<File:/var/folders/bx/6z1z5yks56j40v15n43tjh1c0000gn/T/RackMultipart20130404-53101-3c2whv-0>,
#headers="Content-Disposition: form-data; name=\"image[picture][]\"; filename=\"background-pic.jpg\"\r\nContent-Type: image/jpeg\r\n",
#content_type="image/jpeg",
#original_filename="background-pic.jpg">,
#<ActionDispatch::Http::UploadedFile:0x10c986d60 #tempfile=#<File:/var/folders/bx/6z1z5yks56j40v15n43tjh1c0000gn/T/RackMultipart20130404-53101-bvdysw-0>,
#headers="Content-Disposition: form-data; name=\"image[picture][]\"; filename=\"bible-banner.png\"\r\nContent-Type: image/png\r\n",
#content_type="image/png",
#original_filename="bible-banner.png">],
"album_id"=>"10"},
"authenticity_token"=>"dr8GMCZOQo4dQKgkM4On2uMs8iORQ68vokjW0e4VvLY=",
"commit"=>"Submit",
"utf8"=>"✓",
"album_id"=>"10"}
I would greatly appreciate any help you can share!
#album.images.build will only create the object, it will not save it to the database. When you exit your each loop, #image is just the last image that was built (which would be the last image in the params).
Using #album.images.create will actually save the image to database so that it is persisted like you expect; however, your logic after the each block won't be valid anymore, since you'll only be verifying that the last image was saved. You should check if each image is saved inside of the each block, and somehow record which ones were unsuccessful if that is what you want to do.

Rails 3 render partial outside view?

I am working on a multisite for a client for a skateboarding website. So far everything is great but I am starting to get stuck on the whole partial thing. I have a site and site has_many :albums(Album also belongs to site) but when I try to render albums from a site on the sites homepage i get undefined method `model_name' for NilClass:Class?
I have am trying to render albums/_album.html.erb on the sites/show page to display a site latest's album on the homepage of the site.
Albums Controller
class AlbumsController < ApplicationController
def index
#albums = Album.all
end
def show
#album = Album.find(params[:id])
end
def new
#album = Album.new
end
def edit
#album = Album.find(params[:id])
end
def create
#album = current_site.albums.build(params[:album])
if #album.save
redirect_to albums_path, :notice => 'Album was successfully created.'
end
end
def update
#album = Album.find(params[:id])
if #album.update_attributes(params[:album])
redirect_to album_path(#album), :notice => 'Album was successfully updated.'
end
end
def destroy
#album = Album.find(params[:id])
#album.destroy
end
end
Sites Controller
class SitesController < ApplicationController
def index
#sites = Site.all
end
def show
#site = Site.find_by_subdomain!(request.subdomain)
end
def new
#site = Site.new
end
def edit
#site = Site.find(params[:id])
end
def create
#site = Site.new(params[:site])
if #site.save
redirect_to #site, :notice => 'Signed up!'
end
end
def update
#site = Site.find(params[:id])
if #site.update_attributes(params[:site])
redirect_to #site, :notice => 'Site was successfully updated.'
end
end
def destroy
#site = Site.find(params[:id])
#site.destroy
end
end
Site Show.html
<p id="notice"><%= notice %></p>
<p>
<b>First name:</b>
<%= #site.first_name %>
</p>
<p>
<b>Last name:</b>
<%= #site.last_name %>
</p>
<p>
<b>Subdomain:</b>
<%= #site.subdomain %>
</p>
<%= render :partial => 'albums/album'%>
<%= link_to 'Edit', edit_site_path(#site) %> |
<%= link_to 'Back', sites_path %>
Albums/_album.html.erb
<%= div_for #album do %>
<h2><%= #album.title %></h2>
<%= image_tag #album.photo.url(:small) %>
<% end %>
Am I missing something in my albums controller?
In your show.html, you need to pass in the collection of albums to the render method
<%= render :partial => 'albums/album', :collection => #site.albums %>
Within the _album.html.erb partial, you need to reference the album attribute as a local attribute, like so
<%= div_for album do %>
<h2><%= album.title %></h2>
...
You can read more about partials here 3.4.5 Rendering Collections

Rails accept_nested_attributes is not functioning

class Invoice < ActiveRecord::Base
attr_accessible :line_items_attributes
accepts_nested_attributes_for :line_items
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :invoice
def prepopulate_with(l_items)
unless l_items.empty?
self.line_items = l_items
end
end
end
class InvoiceController < ApplicationController
def new
#invoice = Invoice.new
if params[:line_items]
line_items = params[:line_items].map{|li| LineItem.find(li)}
#prepopulate_with just set #invoice.line_items = whatever_array_of_line_items that was passed in
#invoice.prepopulate_with(line_items)
end
end
def create
#invoice = Invoice.new(params[:invoice])
if #invoice.save
flash[:notice] = "Successfully created invoice."
redirect_to #invoice
else
render :action => 'new'
end
end
end
Line Item gets created prior to invoice. So they will have their individual IDs. I basically preload Invoice.new with line_items on new action. That nested form of invoice which contains line_items (with id) then gets posted to the create action.
Error:
ActiveRecord::RecordNotFound in InvoicesController#create
Couldn't find LineItem with ID=21 for Invoice with ID=
I've included the relevant section of the form below:
<% f.fields_for :line_items do |li| %>
<%= li.text_field :description %>
<%= li.text_field :amount %>
<% end %>

Resources