Rails 4. Moving form information from one controller (with no model), to another (no model) in order to pass arguments to a method - ruby

I have a simple app where users will be able to purchase items. On item pages there will be an add to cart button. The carts-controller displays the items using REDIS relationship, it has no model. Once the user has reviewed the items and wishes to buy, they will be directed to purchases view where once they pay with stripe, an order will be created in purchases-controller. The purchases controller also has no model.
The user.rb model:
class User < ActiveRecord::Base
has_many :orders
def get_cart_items
cart_ids = REDIS.smembers "cart#{id}"
Item.find(cart_ids)
end
def purchase_cart_items!(recipient_name:, recipient_address:)
get_cart_items.each { |item| purchase!(item, recipient_name, recipient_address) }
REDIS.del "cart#{id}"
end
def purchase!(item, recipient_name, recipient_address)
self.orders.create!(user_email: self.email,
item_id: item.id,
recipient_name: recipient_name)
recipient_address: recipient_address)
end
end
The purchases-controller:
class PurchasesController < ApplicationController
def create
Stripe::Charge.create(
...
)
##########
current_user.purchase_cart_items!(recipient_name:, recipient_address:)
##########
end
end
The carts-controller:
class CartsController < ApplicationController
def create
REDIS.sadd current_user_cart, params[:item_id]
end
def destroy
REDIS.srem current_user_cart, params[:item_id]
end
end
Creating the Order attributes of user_email: and item_id: is easy by calling self.email and item.id since they are attached to a model, but I am unsure of how to use a form in the carts view to have the user input the recipient_name: and recipient_address: arguments for purchase_cart_items!(recipient_name:, recipient_address:) method in the purchases-controller.
I need to:
Create a form in the carts view where the user can input arguments for a method in another controller
Upon submission of the form details, I need the user to be redirected to purchases view
The form information has to be then passed as an argument to a method in the purchases-controller
Thanks!

A form_tag with a GET method needs to be used in order to submit the form info to the right controller.
In the carts view:
<%= form_tag({controller: "purchases", action: "new"}, method: "get") do %>
<%= label_tag 'recipient name: ' %>
<%= text_field_tag 'recipient_name' %>
<%= label_tag 'recipient address: ' %>
<%= text_field_tag 'recipient_address' %>
<%= submit_tag "PAY" %>
<% end %>
Once in the new action of the purchases-controller I used params to get the form info and session to make the form information available to the create action in the same controller:
class PurchasesController < ApplicationController
def new
#name = params[:name]
#address = params[:address]
session[:name] = #name
session[:address] = #address
end
def create
Stripe::Charge.create(
...
)
##########
#name = session[:name]
#address = session[:address]
current_user.purchase_cart_destinations!(name: #name, address: #address)
##########
end
end
Once the form information was accessible in the create action of purchases-controller, the keyword arguments for current_user.purchase_cart_items! were easy to set with instance variables of #name and #address.

Related

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

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

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.

Not showing created blog entries

I'm fairly new to Rails and learning to create a blog using this tutorial. On step 10, once I define create and show, after creating a new post in browser I don't see any entries on show with id page. All I see is heading and and blank title and post header.
Following is my controller -
class PostController < ApplicationController
def index
end
def new
end
def create
#post = Post.new(params[:posts])
#post.save
redirect_to #post
end
def show
#post = Post.find(params[:id])
end
end
Show view ---
<h1>Show a post</h1>
<p>
<strong>Title:</strong>
<%= #post.title %>
</p>
<p>
<strong>Text:</strong>
<%= #post.text %>
</p>
Route ---
RailsBlog::Application.routes.draw do
resources :post
root :to => "post#index"
end
Form ---
<%= form_for :post, url: {action: 'create'} do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit 'Submit' %>
</p>
<% end %>
May be this is just a spelling mistake, but since I've recently started learning Rails, I'm unable to resolve this.
Update: I can go to particular id using
http://localhost:3000/post/1
but am only seeing blank page with view headers
The problem is here:
#post = Post.new(params[:posts])
It should be params[:post] - singular, not plural.
Also note that the best practice with form_for is to pass an object instead of symbol:
form_for #post do |f|
Then:
You don't need to specify url
You can reuse the same form for an edit action or create action (if object creation failed due to failing validation)
This however requires to initialize new object in your new action:
def new
#post = Post.new
end
UPDATE:
Also your routes are incorrect. When defining plural resources, you need to use plural form (it's more the convention than requirement, but since you're learning stick with it). So change your routes to:
resources :posts
And rename your controller to PostsController (remember to rename file name as well). restart the server and all should work.
ANOTHER UPDATE:
You also need to rename folder app/views/post to app/view/posts.
AND YET ANOTHER UPDATE:
In rails 4, you are not allowed to mass assign any params which has not been whitelisted using strong parameters. You need to tell rails which fields you allow to be assigned first - this is a security thing. You need to make some changes to your controller:
class PostsController < ApplicationController
...
def create
#post = Post.new(post_params)
...
end
...
private
def post_params
params.require(:post).permit(:title, :text)
end
end
This is the way to tell your controller that you are expecting those attributes from your form and they can be safely assigned.
I had just similar problem on the same tutorial.
The code spelling was correct and clearly accorded to examples in tutorial and BroiSatse's answer above.
The mistake was in order of private method definition.
How it was:
...
private
def post_params
params.require(:post).permit(:title, :text)
end
def show
#post = Post.find(params[:id])
end
...
The working order:
def show
#post = Post.find(params[:id])
end
private
...
Anyway, this topic was rather helpful. Thak you for your answers!

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