Mongoid_slug Rails 4 update / destroy object - ruby-on-rails-4

I am using mongoig_slug gem in my rails app.
I can create an object with the proper url but I can't update / delete the object and I have no error message in the console. (I can edit the form, which contains the correct data)
I even used PRY to check in the console and when I check #book exits and is correct, if I do #book.destroy it says true but does not destroy.
For the edit, I also checked #book, I also checked book_params which is correct.
class Book
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Slug
field :_id, type: String, slug_id_strategy: lambda {|id| id.start_with?('....')}
field :name, type: String
slug :name
end
class BooksController < ApplicationController
before_filter :get_book, only: [:show, :edit, :update, :destroy]
def update
if #book.update_attributes(book_params)
redirect_to book_path(#book)
else
flash.now[:error] = "The profile was not saved, please try again."
render :edit
end
end
def destroy
binding.pry
#book.destroy
redirect_to :back
end
def book_params
params.require(:book).permit(:name)
end
def get_book
#book = Book.find params[:id]
end
end

You can't just copy that line slug_id_strategy: lambda {|id| id.start_with?('....')} without changes. You should replace dots with something that defines is it id or not.
From docs:
This option should return something that responds to call (a callable) and takes one string argument, e.g. a lambda. This callable must return true if the string looks like one of your ids.
So, it could be, for example:
slug_id_strategy: lambda { |id| id.start_with?('5000') } # ids quite long and start from the same digits for mongo.
or:
slug_id_strategy: lambda { |id| =~ /^[A-z\d]+$/ }
or probably:
slug_id_strategy: -> (id) { id =~ /^[[:alnum:]]+$/ }
Updated
The latest version of mongoid_slug is outdated, you should use github version. So in your Gemfile:
gem 'mongoid_slug', github: 'digitalplaywright/mongoid-slug'
Also change field: _id line to:
field :_id, type: BSON::ObjectId, slug_id_strategy: lambda { |id| id =~ /^[[:alnum:]]+$/ }
Cause _id type is not a string, and this occurs error. This should work.

Related

Crud operation "Delete" don't works in my first project in ruby on rails

I'm implementing in my first project the crud operation "delete", but the system take the command as a "get", whitout errors.
this is the controller:
class MoviesController < ApplicationController
def index
#movies = Movie.all
end
#GET /movies/:id
def show
id = params[:id]
#movie = Movie.find(id)
#render plain: 'show'+params[:id]
end
#POST /movies/
# skip_before_action :verify_autenticity_token:
def create
#movie = Movie.create!(params[:movie].permit(:title, :rating, :description, :realease_date))
flash[:notice] = "#{#movie.title} was successfully created."
redirect_to movies_path
end
#new
def new
end
def edit
id = params[:id]
#movie = Movie.find(id)
end
def update
id = params[:id]
#movie = Movie.find(id)
if #movie.update_attributes!(params[:movie].permit(:title,:rating,:realease_date))
flash[:notice] = "#{#movie.title} has been edited."
redirect_to movies_path
end
end
def destroy
id = params[:id]
#movie = Movie.find(id)
#movie.destroy
flash[:notice] = "#{#movie.title} has been deleted."
redirect_to movies_path
end
end
this is the view where is possible call "Delete" operation:
%h2 Details about #{#movie.title}
%ul#details
%li
Rating:
= #movie.rating
%li
Released on:
= #movie.realease_date#.strftime("%B %d, %Y")
%h3 Description:
%p#description= #movie.description
%h4 Reviews:
- if #movie.reviews.empty?
%p
No reviews for this movie...
-else
- #movie.reviews.each do |r|
- u = Moviegoer.find(r.moviegoer_id)
<b>#{r.vote}</b> (<i>#{Moviegoer.find(r.moviegoer_id).name}</i>) #{r.message} <br />
= link_to 'Add review', new_movie_review_path(#movie)
<br/><br/>
#{link_to 'Edit info', edit_movie_path(#movie)} - #{link_to 'Delete', movie_path(#movie), :method => :delete} - #{link_to 'Back to movie list', movies_path}
The problem probably is in the last line, because in server command line "Delete" isn't calls.
this is the server response (is the last GET that is execute when i press "Delete" on the browser) :
enter image description here
file routes.rb:
Rails.application.routes.draw do
resources :movies do
resources :reviews, only: [:new, :create, :destroy]
end
resources :moviegoers
root :to => redirect('/movies')
end
Result of command "rake routes":
Prefix Verb URI Pattern Controller#Action
movie_reviews POST /movies/:movie_id/reviews(.:format) reviews#create
new_movie_review GET /movies/:movie_id/reviews/new(.:format) reviews#new
movie_review DELETE /movies/:movie_id/reviews/:id(.:format) reviews#destroy
movies GET /movies(.:format) movies#index
POST /movies(.:format) movies#create
new_movie GET /movies/new(.:format) movies#new
edit_movie GET /movies/:id/edit(.:format) movies#edit
movie GET /movies/:id(.:format) movies#show
PATCH /movies/:id(.:format) movies#update
PUT /movies/:id(.:format) movies#update
DELETE /movies/:id(.:format) movies#destroy
moviegoers GET /moviegoers(.:format) moviegoers#index
POST /moviegoers(.:format) moviegoers#create
new_moviegoer GET /moviegoers/new(.:format) moviegoers#new
edit_moviegoer GET /moviegoers/:id/edit(.:format) moviegoers#edit
moviegoer GET /moviegoers/:id(.:format) moviegoers#show
PATCH /moviegoers/:id(.:format) moviegoers#update
PUT /moviegoers/:id(.:format) moviegoers#update
DELETE /moviegoers/:id(.:format) moviegoers#destroy
root GET / redirect(301, /movies)
rails_service_blob GET /rails/active_storage/blobs/:signed_id /*filename(.:format) active_storage/blobs#show
rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
rails_direct_uploads POST /rails/active_storage /direct_uploads(.:format) active_storage/direct_uploads#create
It's hard to say exactly what's going on without seeing the corresponding routes. You might try running rake routes to see what the available routes are.
Based on the comments in your controller, I'm guessing you probably have to do something like pluralize the path link_to 'Delete', movies_path(id: #movie.id), method: :delete, but that would only fix the problem if your resource routes are misconfigured.
In order for the link to work as you have it configured your route file would either have to contain resources :movies, or a specially configured route to map to the delete action.

Rails 4 - Loop through deeply nested params to prevent update of password field to blank

In an edit form, an existing password appears in the form as blank, which seems to be the default Rails behavior. I'm trying, however, to avoid the password (or in my case passwords) from being updated to blank if a new password isn't entered, similar to what is described here:
Rails Activerecord update saving unedited field as blank
The difference for me is that the password field is more deeply nested and there is more than one.
Basically what I have is a small bank transfer app where for every :transfer there are two :transfer_accounts, source and destination (transfer_accounts is a "has_many, through" join table for transfers and accounts) and both transfer accounts have an :account with a :password attribute.
My attempt was something like this at the top of the update action:
params[:transfer][:transfer_accounts_attributes].each do |k, v|
v[:account_attributes][:password].delete if v[:account_attributes][:password].empty?
end
which didn't work. Either password left blank is updated to blank.
How would I iterate through the params and prevent either or both passwords from updating if they are left blank?
Here is my controller:
class TransfersController < ApplicationController
def new
#transfer = Transfer.new
#transfer.transfer_accounts.build(account_transfer_role: 'source').build_account
#transfer.transfer_accounts.build(account_transfer_role: 'destination').build_account
#valid_banks = Bank.all.collect {|c| [c.name, c.id]} # available banks seeded in database
end
def index
#transfers = Transfer.all
end
def show
#transfer = resource
end
def create
#transfer = Transfer.new(transfer_params)
if #transfer.save
redirect_to transfers_path, notice: "Transfer Created"
else
redirect_to transfers_path, alert: "Transfer Not Created"
end
end
def edit
resource
#valid_banks = Bank.all.collect {|c| [c.name, c.id]} # available banks seeded in database
end
def update
if resource.update_attributes(transfer_params)
redirect_to transfers_path(resource), notice: "Transfer Updated"
else
redirect_to edit_transfer_path(resource), alert: "Transfer Not Updated"
end
end
def destroy
resource.destroy
end
private
def resource
#transfer ||= Transfer.find(params[:id])
end
def transfer_params
params.require(:transfer).
permit(:name, :description,
transfer_accounts_attributes:
[:id, :account_transfer_role,
account_attributes:
[:id, :bank_id, :name, :description, :user_name,
:password, :routing_number, :account_number
]
])
end
end
params[:transfer][:transfer_accounts_attributes].each do |k, v|
v[:account_attributes].delete(:password) if v[:account_attributes][:password].blank?
end
You have to call hash.delete, rather than delete the contents of the already blank value. Also .blank? is your friend, since that will take care of nil and == ''.

Rspec/Capybara: Devise user 'dissapears' after logging in

I'm having a bizarre problem where I create an admin user for an Rspec/Capybara test and the user is mysteriously deleted after logging in. When I run my test, the user is created and successfully logged in. However when Capybara visits the admin_categories_path, the test fails. Rails raises an exception because current_user is not defined. When I insert a binding.pry, I can see that the user exists up until they are logged in, at which point the user disappears from the test database, causing current_user to be undefined, and thereby triggering an exception. I'm at a loss as to how/why this is happening.
UPDATE: I've gotten past the problem of the user being deleted. Now when Capybara sees the admin_categories_path page, category isn't displayed. Inserting a binding.pry reveals that category is present in the database.
When I launch the app and log in manually, I have no problems accessing admin_categories_path.
Here is my spec file:
require 'rails_helper'
feature 'admin edits category', %Q{
As an admin, I want to edit a category or subcategory, so that it better
represents the content under it.
Acceptance Criteria:
* [X] - I can edit the name of a category inline.
* [X] - I can edit the name of a subcategory inline.
} do
let(:admin) do
FactoryGirl.create(:user, admin: true)
end
let(:category) do
FactoryGirl.create(:category)
end
scenario 'admin edits category title', js: true, focus: true do
login_as(admin)
visit admin_categories_path
bip_area category, :name, 'Test Category'
expect(page).to have_content 'Test Category'
end
end
Here is my helper file authentication.rd:
module Helpers
module Authentication
def log_in_as(user)
visit new_user_session_path
within "#new_user" do
fill_in 'user[email]', with: user.email
fill_in 'user[password]', with: user.password
click_on "Log in"
end
end
end
RSpec.configure do |config|
config.include Authentication, :type => :feature
end
end
Here is my user factory:
require 'factory_girl'
FactoryGirl.define do
factory :user do
sequence(:email) {|n| "lafiel.abriel#{n}#abhnation.com" }
username
password 'password'
password_confirmation 'password'
end
factory :category do
sequence(:name) { |n| "Category ##{n}" }
parent_id nil
display_index 1
user
end
sequence :username do |n|
"Lafiel_Abriel_#{n}"
end
end
And here is my application_controller.rd where the exception is triggered:
module Admin
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :reject_unless_admin
layout 'admin/layouts/application'
protect_from_forgery with: :exception
helper :avatar, :devise, :admin
protected
def reject_unless_admin
unless current_user && current_user.admin?
raise ActionController::RoutingError.new('404: Not Found')
end
end
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << [
:avatar,
:remote_avatar_url,
:email,
:username,
:first_name,
:last_name,
:age,
:website,
:password,
:password_confirmation,
:current_password
]
end
end
end
Any help/insight is greatly appreciated. Thanks.
If I recall correctly, this problem had something to do with the test database schema being out of sync with the development database. Try running rake db:test:prepare to ensure your test schema matches your development schema.
In my specific case, I had a two problems to overcome.
The first problem occurred when I created a category and an admin in a let block. Those needed to be placed in a before block so that they were created before the test executed. When I placed those variables in let blocks, they were not being created until the first time they were called in the test. So when I logged in as an admin, the admin variable was being passed to my login_as helper method before it was saved to the database. The same is true for the category variable.
This code doesn't work for me:
let(:admin) do
FactoryGirl.create(:user, admin: true)
end
let(:category) do
FactoryGirl.create(:category)
end
scenario 'admin edits category title', js: true, focus: true do
login_as(admin)
visit admin_categories_path
bip_area category, :name, 'Test Category'
expect(page).to have_content 'Test Category'
end
This is solution I used to overcome the first problem:
before(:each) do
#admin = FactoryGirl.create(:user, admin: true)
#category = FactoryGirl.create(:category, user: #admin)
end
scenario 'admin edits category title', js: true do
skip "Doesn't work yet."
login_as(#admin)
visit admin_categories_path
wait_for_ajax
bip_text #category, :name, 'Test Category'
expect(page).to have_content 'Test Category'
end
The second problem is that when my test hits the admin/categories_controller.rb, Category.all returns an empty array, even though #category.save! and #category.valid? return true when I test my before block. I was never able to find a solution to the problem and ended up putting the test on ice.
I think part of the problem is that the documentation for the Best-in-Place gem is not very good. I used it at the time because it was an easy solution to my problem. Going forward though, I would use something like angular for this use-case. The documentation is much better and it's not a black box like a lot of gems are.
I'm not sure what bip_area does, but since your test says that it's editing a category I assume you are expecting the category to be present on the page when you visit admin_categories_path. When you visit that path however category has not yet been created since you are using 'let' which is lazily evaluated (created at time of first use of the variable), so it would not be shown on the screen for you to edit. Using binding.pry and then looking at that variable would actually create it, so it might be confusing you into thinking it was there. You can use 'let!' instead which will force the variable to be created before each test instead of lazily evaluating it.

Rails 4.0.4: How do I display whether or not the creation of a record was successful?

So After submitting a form for my Testimony model I get the following activity in my rails console:
Processing by TestimoniesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Y7n/+rlDoH3ys68HMOh6T6WFpAelRT18WUPstCz41vE=", "testimony"=>{"first_name"=>"bob ", "last_name"=>"balaban", "email"=>"", "contact_number"=>"", "country"=>"", "question1"=>"", "question2"=>"", "question3"=>"", "question4"=>"", "question5"=>"", "signature"=>"", "waiver"=>"0"}, "commit"=>"Save Testimony"}
(0.1ms) begin transaction
Testimony Exists (0.2ms) SELECT 1 AS one FROM "testimonies" WHERE ("testimonies"."last_name" = 'balaban' AND "testimonies"."first_name" = 'bob ' AND "testimonies"."email" = '') LIMIT 1
(0.1ms) rollback
Here is TestimoniesController
class TestimoniesController < ApplicationController
def new
end
def index
end
def show
#testimony = Testimony.find(params[:id])
end
def create
#testimony = Testimony.new(post_params)
#testimony.save
redirect_to #testimony
end
private
def post_params
params.require(:testimony).permit(:first_name, :email, :last_name, :contact_number, :country, :question1, :question2, :question3, :question4, :question5, :signature, :waiver)
end
end
Here is my Model file for Testimony.rb
class Testimony < ActiveRecord::Base
validates_presence_of :first_name, :last_name,:email, :contact_number, :country, :question1, :question2, :question3, :question4, :question5, :signature, :waiver
validates_uniqueness_of :last_name, :scope => [:first_name, :email]
end
I would like to display a message such as:
Record already exists
Or
All the fields need to be filled out
I realize this is pretty pathetic but I thought it was part of Rails magic to assert the uniqueness and presence of all of the fields of a Model at the form level, before you perform a #modelname.save. I clearly don't really understand. Thanks for your help.
if #modelname.save! failed it'll raise an exception... the exception name varies depending on the orm you might be using. but most likely it'll be ActiveModel::Validations
you can use the following pattern i myself use
#testimony.save!
redirect_to #testimony
rescue_from ActiveModel::Validations do |ex|
# here you can flash the message do what ever you want when saving fails
render json: {message: 'Record already exists', status: :unprocessable_entity
end
other solution is to use the following
if #testimony.save
redirect_to #testimony
else
render json: {message: 'Record already exists', status: :unprocessable_entity
end
[EXTRA INFO YOU CAN IGNORE :D]
I use the first solution cause i can handle exceptions in the application_controller.
for example you can add those to the application_controller to handle those failing cases
rescue_from CanCan::AccessDenied, with: :render_access_denied
rescue_from Mongoid::Errors::DocumentNotFound, with: :render_not_found
rescue_from ActionController::RoutingError, with: :render_not_found
rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
error = {}
error[parameter_missing_exception.param] = ['parameter is required']
render_bad_request error
end
where render_XXX are methods defined in the application controller.

Does ActiveModel::Serializer require an explicit render call?

I know that when using view templates (html, rabl), I don't need an explicit render call in my controller action because by default, Rails renders the template with the name corresponding to the controller action name. I like this concept (not caring about rendering in my controller code) and therefore wonder whether this is possible as well when using ActiveModel::Serializers?
Example, this is code from a generated controller (Rails 4.1.0):
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
#other actions
# GET /products/1
# GET /products/1.json
def show
end
end
and this is the serializer:
class ProductSerializer < ActiveModel::Serializer
attributes :id, :name, :description, :url, :quantity, :price
end
Hitting /products/1.json, I would expect two things to happen:
Fields not listed in the serializer to be ommited,
Whole JSON object to be incapsulated within a 'product' top level field.
However, this does not happen, whole serializer is ignored. But then if I modify the Show method to the following:
# GET /products/1
# GET /products/1.json
def show
#product = Product.find(params[:id])
respond_to do |format|
format.html
format.json { render json: #product }
end
end
And now it is all fine, but I have lost the benefit of the before_action filter (and it seems to me that I have some redundant code).
How should this really be done?
Without an explicit render or respond_with or respond_to Rails will look for a matching template. If that template does not exist Rails throws an error.
However, you can create your own resolver to bypass this. For instance, suppose you created app\models\serialize_resolver.rb and put this into it:
class SerializeResolver < ActionView::Resolver
protected
def find_templates(name, prefix, partial, details)
if details[:formats].to_a.include?(:json) && prefix !~ /layout/
instance = prefix.to_s.singularize
source = "<%= ##{instance}.active_model_serializer.new(##{instance}).to_json.html_safe %>"
identifier = "SerializeResolver - #{prefix} - #{name}"
handler = ActionView::Template.registered_template_handler(:erb)
details = {
format: Mime[:json],
updated_at: Date.today,
virtual_path: "/#{normalize_path(name, prefix)}"
}
[ActionView::Template.new(source, identifier, handler, details)]
else
[]
end
end
def normalize_path(name, prefix)
prefix.present? ? "#{prefix}/#{name}" : name
end
end
And then, in either your application controller (or in an individual controller) place:
append_view_path ::SerializeResolver.new
With that you should be able to do what you want. If it is a json request, it will create an erb template with the right content and return it.
Limitations:
This is a bit clunky because it relies on erb, which is not needed. If I have time I will create a simple template handler. Then we can invoke that without erb.
This does wipe out the default json response.
It relies on the controller name to find the instance variable (/posts is converted to #post.)
I've only tested this a little. The logic could probably be smarter.
Notes:
If a template is present, it will be used first. That allows you to override this behavior.
You can't simply create a new renderer and register it, because the default process doesn't hit it. If the template is not found, you get an error. If the file is found, it goes straight to invoking the template handler.
The 'redundant code' we see in the second one, is this line only:
#product = Product.find(params[:id])
And I believe this is the same logic as your before_action. You don't need this line, just remove it. Now the duplication is removed.
To the remaining part. An action needs to know what to render. By default if the action is empty or absent, the corresponding 'action_name'.html.erb (and other formats specified by respond_to) will be looked up and rendered.
This is why what the Rails 4 generator created works: it creates the show.html.erb and show.json.jbuilder which get rendered.
With ActiveModel::Serializer, you don't have a template. If you leave the action empty, it doesn't have a clue what to render. Thus you need to tell it to render the #product as json, by either:
render json: #product
or
respond_with #product