using strong parameters for a server-side autocomplete resource - ruby-on-rails

playing around with this railscast, except I'm using Rails 5 where attr_accessible is deprecated. VenueSuggestion is a resource for suggesting venues in the db as user types something in the related field of a form. the problem I'm having right now is that as I start typing things that match db contents, there are no search results.
the model:
class VenueSuggestion < ApplicationRecord
# should anything go in place of attr_accessible?
def self.terms_for(prefix)
suggestions = where("term like ?", "#{prefix}_%")
suggestions.order("popularity desc").limit(10).pluck(:term)
end
def self.index_venues
Venue.find_each do |venue|
index_term(venue.name)
index_term(venue.address)
venue.name.split.each { |t| index_term(t) }
end
end
def self.index_term(term)
where(term: term.downcase).first_or_initialize.tap do |suggestion|
suggestion.increment! :popularity
end
end
end
the controller:
class VenueSuggestionsController < ApplicationController
#is this right?
def create
VenueSuggestion.create(params[:venue_suggestion])
end
def index
render json: VenueSuggestion.terms_for(params[:term])
end
# is this right?
private
def venue_suggestion_params
params.require(:venue_suggestion).permit(:term, :popularity)
end
end
the rake task:
namespace :venue_suggestions do
desc "Generate venue suggestions for event form"
task :index => :environment do
VenueSuggestion.index_venues
end
end
what the log shows:
Started GET "/venue_suggestions?term=sp" for ::1 at 2016-05-25 21:27:31 -0400
Processing by VenueSuggestionsController#index as JSON
Parameters: {"term"=>"sp"}
(1.4ms) SELECT "venue_suggestions"."term" FROM "venue_suggestions" WHERE (term like 'sp_%') ORDER BY popularity desc LIMIT $1 [["LIMIT", 10]]
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Attributes (0.06ms)
Completed 200 OK in 4ms (Views: 0.6ms | ActiveRecord: 1.4ms)

The strong params features provides an interface for protecting attributes from end-user assignment. This makes Action Controller parameters forbidden to be used in Active Model mass assignment until they have been whitelisted.
class PeopleController < ActionController::Base
# Using "Person.create(params[:person])" would raise an
# ActiveModel::ForbiddenAttributes exception because it'd
# be using mass assignment without an explicit permit step.
# This is the recommended form:
def create
Person.create(person_params)
end
# This will pass with flying colors as long as there's a person key in the
# parameters, otherwise it'll raise an ActionController::MissingParameter
# exception, which will get caught by ActionController::Base and turned
# into a 400 Bad Request reply.
def update
redirect_to current_account.people.find(params[:id]).tap { |person|
person.update!(person_params)
}
end
private
# Using a private method to encapsulate the permissible parameters is
# just a good pattern since you'll be able to reuse the same permit
# list between create and update. Also, you can specialize this method
# with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end
Keep in mind that you will need to specify term inside venue_suggestions with something like
params = ActionController::Parameters.new(venue_suggestion: { term: "something" })
Source: http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html

Related

Shopify App causing return code 302

I have this piece of code which is triggered when a new order is created in my Shopify store:
class CustomwebhooksController < ShopifyApp::WebhooksController
#the line below is to avoid the CSR error
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
include ShopifyApp::WebhookVerification
# This method is triggered when the user completes the checkout process
# Gets the order number and see the message "Your order is confirmed"
# I should use this as the trigger to update the other SKU for the DT solution:
def orders_create
request.body.rewind
data = JSON.parse(request.body.read)
#redirect_to controller: "home", action: "update_stock", status: :found
#MyHomeController = HomeController.new
#MyHomeController.update_stock
#do not remove line below
head :ok
end
end
This is the code in my home controller:
class HomeController < ShopifyApp::AuthenticatedController
def update_stock
#products = ShopifyAPI::Product.find(:all, :params => {:title => 'DT382'})
#products.each do |product|
puts(product.id)
end
end
end
Somehow the call to #MyHomeController.update_stock generates a 302 as shown below:
Rendered inline template (0.4ms)
Completed 302 Found in 689ms (Views: 0.9ms | ActiveRecord: 0.0ms)
Am I missing anything?
I would like to challenge your design for a minute.
Firstly, if you're going to redirect to a different controller/action, why aren't you using redirect_to update_stock_path where the path is specified in your routes file?
Secondly, why redirect at all? You're receiving a webhook with some payload and then you need to hit the Shopify API to get product data. To me, this is far more suitable as a background job. Furthermore, designing this as receive webhook -> schedule background job -> do work in the job will be more scalable than what you're doing now.
I managed to figure out the solution after carefully reading the Shopify API documentation.
class HomeController < ShopifyApp::AuthenticatedController
def update_stock
shop_url = "https://#{ENV['API_KEY']}:#{ENV['API_SECRET']}#myshopifyshop.myshopify.com/admin"
ShopifyAPI::Base.site = shop_url
#products = ShopifyAPI::Product.find(:all, :params => {:title => 'DT382'})
#products.each do |product|
puts(product.id)
end
ShopifyAPI::Base.clear_session
end
end

Rails API Versioning, Routing issue

I'm trying to implement a simple rails api app with versioning, based on Railscasts #350 episode. I wanted to access a specific API version of the app by providing the version through Accept header in this format: application/vnd.rails5_api_test.v1. And when no Accept header is provided the request will be routed to the current default version of the app. To handle this I have created an api_constraints file in my lib directory which is to be required in routes.
I have created two versions of the app v1 and v2 in which, v1 has the Users resource and v2 has Users and Comments resources.
Everything was working as expected, except for when I request URL localhost:3000/comments by passing version 1 through the headers using Postman, I'm getting the response from the comments resource, displaying all the comments. But I'm expecting the response to be status: 404 Not Found, as the comments resource was in version 2 and the requested version is 1.
This is the response from the server:
Started GET "/comments" for 127.0.0.1 at 2016-04-01 20:57:53 +0530
Processing by Api::V2::CommentsController#index as application/vnd.rails5_api_test.v1
Comment Load (0.6ms) SELECT "comments".* FROM "comments"
[active_model_serializers] User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::JsonApi (4.32ms)
Completed 200 OK in 7ms (Views: 5.0ms | ActiveRecord: 1.5ms)
Here are my working files:
Constraints file, lib/api_constraints.rb:
class APIConstraints
def initialize(options)
#version = options[:version]
#default = options[:default]
end
def matches?(req)
req.headers["Accept"].include?(media_type) || #default
end
private
def media_type
"application/vnd.rails5_api_test.v#{#version}"
end
end
Routes file, config/routes.rb:
Rails.application.routes.draw do
require "api_constraints"
scope module: 'api/v1', constraints: APIConstraints.new(version: 1) do
resources :users
end
scope module: 'api/v2', constraints: APIConstraints.new(version: 2, default: true) do
resources :users
resources :comments
end
end
Users controller for v1, api/v1/users_controller.rb:
class Api::V1::UsersController < ApplicationController
def index
#users = User.all
render json: #users, each_serializer: ::V1::UserSerializer
end
end
Users controller for v2, api/v2/users_controller.rb:
class Api::V2::UsersController < Api::V1::UsersController
def index
#users = User.all
render json: #users, each_serializer: ::V2::UserSerializer
end
end
Comments controller for v2, api/v2/comments_controller.rb:
class Api::V2::CommentsController < ApplicationController
def index
#comments = Comment.all
render json: #comments, each_serializer: ::V2::CommentSerializer
end
end
user serializer for v1, user_serializer.rb:
class V1::UserSerializer < ActiveModel::Serializer
attributes :id, :name, :email
end
user serializer for v2, user_serializer.rb:
class V2::UserSerializer < V1::UserSerializer
has_many :comments
end
comments serializer for v2, comment_serializer.rb:
class V2::CommentSerializer < ActiveModel::Serializer
attributes :id, :description
belongs_to :user
end
I have tried removing the default: true option in the routes and then it is working as expected. But I want it to work with default option.
Could anyone please let me know where I'm getting this wrong and also share your thoughts on this approach. If this one is not the best way to do, then guide me through the correct way of implementing it. Thanks in advance for anyone who takes time in helping me out. :) Cheers!
I don't think this can be solved easily as the v1 doesn't have comments the v2 will be matched regardless.
Is you APIConstraints using this method?
def matches?(req)
#default || req.headers['Accept'].include?("application/vnd.example.v#{#version}")
end
I think the method is a bit too loose here and should look like this to ignore requests that do have a version.
def matches?(req)
(#default &&
req.headers['Accept'].grep(/^application/vnd.example.v\d+/$).empty?
) || req.headers['Accept'].include?("application/vnd.example.v#{#version}")
end

Rails: How to read the value of a select without a form

I'm running into a perplexing issue that I can only resolve partway, and hopefully, someone more experienced can tell me whether I can achieve what I wish, or if I'm barking up the wrong tree.
I have a Rails 4 application which uses Devise and CanCan. I'd like to make a small subset of application functionality available to guest users (not logged in). I can achieve this by specifying a get route to a controller method and using link_to to reach that method. I cannot, however, figure out how to get the value of a select box to pass along as parameters on that page without making that view a form using form_tag (there is no model associated with this view).
I can pass hardcoded params along like so:
<%= link_to "Month", activities_search_month_path(:resource_id => 4) %>
but I'd rather have something like:
<%= link_to "Month", activities_search_month_path(:foo => :resource_id) %>
where the second symbol refers to the value of a select_tag. This second example delivers a literal value of "resource_id" when I dump the :foo key unless I convert my view to a form.
If I turn the view into a form by enclosing all the erb in a form_tag, I get a 401 Forbidden error, after which the Devise sign in form is rendered. My guess is that any time you want to process a form, Rails (or Devise) demands authentication on some level. The behavior is the same when I use button_to rather than link_to, since button_to wraps itself in a form under the covers.
How can I set that resource_id argument in my link_to, or will I be forced to create a guest user access level and silently log in guest users? It's important for the UX that users can access this functionality with the least amount of effort possible.
Thanks in advance.
Addendum: quick_search method from controller
def quick_search
puts "quick search 0"
if(params[:time_period] == 'today')
#resource = Resource.find(params[:resource_id])
#site = Site.find(params[:site_id])
#time_period_string = "Activities for #{localize_date(Date.today)} at #{#resource.name}, #{#site.name}"
puts "quick search 1"
if user_signed_in?
puts "quick search 2a"
#activities = Activity.where("system_id = ? and start_date = ? and activity_status_id = ? and resource_id = ?", current_system_id, #today, 2, params[:resource_id])
else
puts "quick search 2b"
if(Setting["#{current_subdomain_not_signed_in}.quick_search_guest_access"] == 'true')
puts "quick search 3a"
current_system_id = current_system_id_not_signed_in
#activities = Activity.where("system_id = ? and start_date = ? and activity_status_id = ? and resource_id = ?", current_system_id, #today, 2, params[:resource_id])
else
puts "quick search 3b"
redirect_to '/users/sign_in'
end
end
end
Note: the quick_search method is never entered. CanCan (or maybe Devise) steps in immediately and redirects to sign in:
Console output:
Started GET "/activities/quick_search" for 127.0.0.1 at 2015-04-12 18:01:58 -0700
Processing by ActivitiesController#quick_search as HTML
(0.2ms) SELECT DISTINCT "systems"."subdomain" FROM "systems"
Completed 401 Unauthorized in 1ms
Started GET "/users/sign_in" for 127.0.0.1 at 2015-04-12 18:01:58 -0700
Processing by Devise::SessionsController#new as HTML
(0.2ms) SELECT DISTINCT "systems"."subdomain" FROM "systems"
Rendered layouts/_header.html.erb (0.8ms)
Rendered devise/shared/_links.html.erb (4.1ms)
Rendered devise/sessions/new.html.erb within layouts/application (14.7ms)
Rendered layouts/_footer.html.erb (0.0ms)
Completed 200 OK in 285ms (Views: 282.3ms | ActiveRecord: 0.2ms)
Ability.rb
can :quick_search, Activity
can :search_day, Activity
can :search_week, Activity
can :search_month, Activity
The odd thing is that link_to quick_search fails with a 401, but link_to the other three methods works fine -- I just can't get parameters to them dynamically.
If you are using CanCan(Can?) you can define a special ability for guests.
How does your Ability-model look?
Which controller are handling the action that you want to view?
How do you authenticate with CanCan in this controller?
https://github.com/CanCanCommunity/cancancan/wiki/CanCan-2.0
Under the "Defining Abilities" you can see a non-user example.
Fixing CanCan is probably the best option, if you do not want to:
For the part with the link and select box it would be easiest to handle as a form and then handle the redirect in the controller, it could also be done with a remote ajax form.
http://edgeguides.rubyonrails.org/working_with_javascript_in_rails.html
This should work:
<% form_tag Activity, activity_quick_search_path, remote: true do %>
<%= select_tag :resource_id...%>
<%= submit_tag %>
<%end%>
Edit after comments:
The culprit here is(was) an:
before_action :authenticate_user!
Causing Devise to redirect to sign in page.
However, if you have CanCan you shouldn't need the authenticate_user.
Short example:
With only Devise I would do:
class NewsController < ApplicationController
before_action :authenticate_user!
before_action :set_news, except: [ :index, :new ]
def index
#news = News.all
end
def show
end
def new
#news = News.new
end
def edit
end
def create
#news = News.new(news_params)
flash[:notice] = 'News created' if #news.save!
redirect_to #news
end
def update
#news.update! news_params
redirect_to #news
end
def destroy
#news.destroy!
redirect_to News
end
private
def news_params
params.require(:news).permit(some_attributes)
end
def set_news
#news = News.find(params[:id])
end
end
How it looks with CanCanCan:
class NewsController < ApplicationController
load_and_authorize_resource
def index
end
def show
end
def new
end
def edit
end
def create
flash[:notice] = 'News created' if #news.save!
redirect_to #news
end
def update
#news.update! news_params
redirect_to #news
end
def destroy
#news.destroy!
redirect_to News
end
private
def news_params
params.require(:news).permit(some_attributes)
end
end
Which I find super neat 😄
Hope that this can help as well.

ActiveRecord Rescue from View

I build a little e-commerce solution. At the end of an order, I want the cart to delete, so a user can't access the same cart after ordering. After I render the order_confirmation page, I have an after_action [:show] that deletes the cart. If refreshed, the order confirmation page won't work because the line items(items teh user bought) were destroyed with the cart.
This is fine, I already sent them an email and if they refresh the confirmation page I'd like an activerecord::recordnotfound rescue.
my view looks like this though
<% #order.items.each do |id| %>
<% #line_item = LineItem.find(id) %>
(line item data to show)
When refreshed, the line_items delete and I get an activerecord error.
Couldn't find LineItem with id=8
Since this is being called from the view, and isn't exactly conventional -- how can I rescue to redirect and say something like "Your Cart is Reset" or something along those lines? I tried putting it in the controller but it didn't get touched (or didn't trigger..)
after_action :remove_cart, only: [:show]
def index
#orders = Order.all
end
def show
#order = Order.find(params[:id])
#cart = current_cart
rescue ActiveRecord::RecordNotFound
logger.error "User Refresh Page"
redirect_to products_path
raise
end
If it's super advised not to have a loop like that in the view, should create an array in the controller and then loop through them on the view and create the rescue in the controller?
UPDATE:
remove_cart method is pretty blunt, looks like this.
def remove_cart
#cart = current_cart
#cart.destroy
end
Update
I've followed Damien's instructions but the after commit
gives me this
(6.2ms) COMMIT
undefined method `true=' for #<Cart:0x00000106b9fb98>
Redirected to http://localhost:3000/orders/8
Completed 302 Found in 1933ms (ActiveRecord: 19.4ms)
with order.rb
after_commit :archive_cart, on: :create
private
def archive_cart
cart.archive!
end
and cart.rb as
def archive!
update_attribute(active, false)
end
This is based on our running conversation, here:
https://chat.stackoverflow.com/rooms/55591/discussion-between-peege151-and-damien-roche
1. Update your Order model to include:
class Order
belongs_to :cart
has_many :line_items, through: :cart
end
Then, in your view:
<% #order.line_items.each do |line_item| %>
2. As meager noted, you should archive order details. Add a status to your cart, such as:
add_column :carts, :active, :boolean, default: true
Then, instead of #cart = current_cart in your controller, refer to the cart directly via #order.cart.
3. Move destroy/archive_cart logic into your models
Note: this is not normal behaviour -- Order would usually have a status, and the cart would be archived when that Order is confirmed, but asker is using a preliminary model (OrderPreview), where each new Order is pre-confirmed.
class Order
after_commit :archive_cart, on: :create
private
def archive_cart
cart.archive!
end
end
class Cart
def archive!
update_attribute(:active, false)
end
end

Rails: How to pass parameters from a model using an after_commit method, eventually to share on twitter

I'm trying to use an after_commit method to pass parameters from the post to the user model where it is shared to twitter with another method.
It works fine when I just pass it something from the post model like 'title' or 'content':
post.rb
after_commit :share_all
def share_all
if user.authentications.where(:provider => 'twitter').any?
user.twitter_share(title, content)
end
end
user.rb
def twitter_share(title, content)
twitter.update("#{title}, #{content}")
end
But this is as far as my understanding goes, I've read elsewhere that I could pass 'self' instead of 'title' and 'content', and still be able to use 'title' and 'content' plus any thing else from the model such as 'created_at'. However, I can't seem to get this working, I've tried this :
post.rb
def share_all
if user.authentications.where(:provider => 'twitter').any?
user.twitter_share(self)
end
end
user.rb
def twitter_share(self)
twitter.update("#{title}, #{content}")
end
And I get SyntaxError (/Users/ihal/Desktop/dayor/app/models/user.rb:118: syntax error, unexpected keyword_self, expecting ')'
def twitter_share(self)
And it posts this to twitter #< Post:0x00000101d6e1e0>
My question is how to you properly setup passing 'self' so that any parameter could be called with twitter.update()?
Also how do you go about pulling out URL for the post, so that you could pass the URL to share on twitter?
Edit:
trying Rails.application.routes.url_helpers.post_url(#post, :host => 'myhost.com')
in post.rb
class Post < ActiveRecord::Base # line 19
after_commit :share_all
Rails.application.routes.url_helpers.post_url(#post, :host => 'myhost.com') #line 37
def share_all
if user.authentications.where(:provider => 'twitter').any?
user.twitter_share(self)
end
end
When I go to delete a post, I get the error :
Started POST "/posts/32" for 127.0.0.1 at 2011-04-15 14:57:17 -0700
Processing by PostsController#destroy as HTML
Parameters: {"authenticity_token"=>"x8KkqLLCLdTOouUfCMzyWWmwxLIKThnE1n3rQNSkew8=", "id"=>"32"}
User Load (1.1ms) SELECT "users".* FROM "users" WHERE ("users"."id" = 5) LIMIT 1
Completed in 82ms
ActionController::RoutingError (No route matches {:action=>"destroy", :controller=>"posts"}):
app/models/post.rb:37:in <class:Post>'
app/models/post.rb:19:in'
app/controllers/posts_controller.rb:36:in `authorized_user'
Rendered /Users/ihal/.rvm/gems/ruby-1.9.2-p136#rails3gemset/gems/actionpack-3.0.1/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (1.2ms)
post controller
def destroy
#post.destroy
redirect_to root_path
end
private
def authorized_user
#post = Post.find(params[:id]) #line 36
redirect_to root_path unless current_user?(#spost.user)
end
end
Have you thought about using an Observer? That way you can keep the after_commit stuff, which does not appear to to belong in the model, in its proper place. Plus it helps simplify your model instead of cluttering it up.
For the syntax error, self is a reserved word. Rename the variable name in the method declaration. So try something like:
def twitter_share(post)
# do stuff
end
To access the url helpers outside of the controller, use:
Rails.application.routes.url_helpers.post_url(#post, :host => 'myhost.com')
Don't forget to use the :host option when accessing the url helpers outside the controller so the helper has context.
what I can see in code is, title and content are attributes of Post. so passing them as theire name will be converted into their value.
now you are passing self in twitter_share from post then the post object will be passed to that method. you need to modified the twitter_share method as below to make it work
def twitter_share(post)
twitter.update("#{post.title}, #{post.content}")
end
def twitter_share(post)
twitter.update("#{post.title}, #{post.content}")
end
You are passing a Post into the user.twitter_share method.

Resources