How can I create a new object without a form, using just URL parameters in a Rails friendly way?

时间:2016-07-11 21:39:09

标签: ruby-on-rails ruby-on-rails-4

What would be the most rails friendly way to create a new object strictly from landing on a URL, bypassing the need for a form?

Suppose I have 3 models:

  • Business
  • Customer
  • Rating

A business has many customers, and a customer has one rating.

Example: How would you rate your meal at XYZ Restaurant? 1, 2, or 3? (Each of those being separate links that would save a score of 1/2/3)

What will my rating#new and rating#create methods & routes need to look like to accomplish this?

I was able to get it working by creating a totally new method which you can see below:

Example URL: abc.com/ratings/:customer_token/:score

def rate
@customer = Customer.find_by_token(params[:customer_token])
@business = @customer.business
unless @customer.ratings.exists?
  @rating = @customer.ratings.new
  @rating.customer_id = @customer.id
  @rating.business_id = @business.id
  @rating.score = params[:score].to_i
  @rating.save
  if @rating.save
    redirect_to :action => 'thanks'
  end
end

end

While the above works, I know it isn't a good way to do it. Would love some advice!

Edit: I want to make it clear that the links I'd like to generate will be from an email client, so I can't use ruby logic there or any sort of javascript.

3 个答案:

答案 0 :(得分:0)

The reason your proposed method is risky is it nests all your parameters in the URL get stream without verifying via the post stream. Someone could type in something rediculous in your URL and add something odd, like abc.com/ratings/[legit customer token]/55 and your code would put a rating of 55 out of 3 in your database.

You could still use a form, with hidden variables, to create the rating. You can still have 1, 2, and 3, be the content of links (like buttons) to submit that form.

#in your view
<% 3.times do |i| %>
  <%= form_for @rating, action: :rate do |f| %>
    <%= f.hidden_field :score, value: i %>
    <%= f.hidden_field :customer_id, value: @customer.id %>
    <%= f.hidden_field :business_token, value: @business.id %>
    <%= f.submit i %>
  <% end %>
<% end %>

and

#in your controller
def rate
  @rating = Rating.new(rating_params)
  if @rating.save
    #handle success
  else 
    #handle error
  end
end

private
  def rating_params
    params.require(:rating).permit(:rating, :customer_id, :business_id)
  end

答案 1 :(得分:0)

I give you credit for coming up with a solution that on the surface works, but I can see a problem... if it's actually a link_to that you've created that creates a GET action, then you could end up with unexpected postings from web crawlers!

Imagine Google's spiders following every link on your site... they will find links to a customer, they will find links to rating, and by following the rating link, they'll create a rating! This is why actions that create, change, or delete data should never be behind GETactions.

Consider using button_to helpers which create buttons that essentially do a POST (and modify your routes to accept POST to your show url)

button_to('Rate 1', customer_token: @customer.token, rating: 1)

I guess a customer belongs to a business? @business = @customer.business. If a customer rates two businesses, there are two customer records?

Also, you don't need the first @rating.save ... the second one is a repeat of the save. But that's a minor issue.

答案 2 :(得分:0)

The only option (aside from an AJAX call) would be to use the link_to helper, with a method POST:

link_to 'Rate', ratings_path(score: 1, customer_token: 123), method: :post

Although this will not look like a form in your codebase, Rails will utilise jquery-ujs and will generate a form using JavaScript.

Your controllers will just have to use the params to find the appropriate Customer and add a rating with the score to it.

Hope that helps!