In my Rails app I have made an api for an iOS app to consume.
I have a user model with a association to a profile:
class User < ActiveRecord::Base
has_one :personal_profile
accepts_nested_attributes_for :personal_profile
end
class PersonalProfile < Profile
belongs_to :user
end
The users_controller.rb looks like this:
class Api::V1::UsersController < Api::V1::ApiController
before_filter :authenticate_api_user, only: [:show, :update]
def create
#user = User.new(user_params)
if #user.save
#user
else
render json: { errors: #user.errors.full_messages }, status: 422
end
end
def show
#user
end
def update
if #user.update(user_params)
#user
else
render json: { errors: #user.errors.full_messages }, status: 422
end
end
private
def user_params
params.require(:user).permit(:name, :email, :birthday, :gender, :password, :password_confirmation, personal_profile_attributes: [:website, :location, :description, :tagline, :tag_tokens, :image, :image_cache])
end
def authenticate_api_user
authenticate_or_request_with_http_token do |token, options|
#user = User.find_by(auth_token: token)
end
end
end
When I do a put request like this:
curl -H 'Authorization: Token token=AUTH_TOKEN' -H 'Content-Type: application/json' -X PUT -d '{"user": {"name": "Frank", "personal_profile_attributes": { "tagline": "New tagline" }}}' http://localhost:3000/api/user
I get this response:
{"errors":["Personal profile title can't be blank."]}
And it's like the server doesn't recognize my params:
Started PUT "/api/user" for ::1 at 2016-01-19 10:16:33 +0100
Processing by Api::V1::UsersController#update as JSON
Parameters: {"user"=>{"name"=>"Frank", "personal_profile_attributes"=>{"tagline"=>"New tagline"}}}
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."auth_token" = $1 LIMIT 1 [["auth_token", AUTH_TOKEN]]
(0.8ms) BEGIN
PersonalProfile Load (1.2ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."type" IN ('PersonalProfile') AND "profiles"."user_id" = $1 ORDER BY title asc LIMIT 1 [["user_id", 1]]
SQL (1.3ms) UPDATE "profiles" SET "user_id" = $1, "updated_at" = $2 WHERE "profiles"."id" = $3 [["user_id", nil], ["updated_at", "2016-01-19 09:16:33.629907"], ["id", 1]]
User Exists (1.3ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."email") = LOWER('d#friis.me') AND "users"."id" != 1) LIMIT 1
(0.8ms) ROLLBACK
Completed 422 Unprocessable Entity in 19ms (Views: 0.3ms | ActiveRecord: 6.5ms)
I have another controller setup for the web app, which is pretty standard and works just fine!
Any help is much appreciated!
Okay, so the problem was that I did not pass an ID for the associated record with the request params. This meant that a new associated record was created with the attributes I was trying to update.
Setting the ID solved the problem and updated the record as expected.
{
"user" => {
"personal_profile_attributes" => {
"id" => 1,
"tagline" => "New tagline"
}
}
}
Related
My method is executing, but Devise is not using the return value at all. On the sign in page, it just reloads the page with a 'Signed in successfully' notice. It doesn't redirect to the value returned from the method.
Log
Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 22:19:50 -0500
Processing by Users::SessionsController#create as HTML
Parameters: {"utf8"=>"√", "authenticity_token"=>"tQd5a43StP85oyyCpEmFU8cAkFXdJL2OLpuAK1+sqQC6/rIqcd+fB2iE4RT0RoPKPCqreNBYlv2bxjl9gZFrWg==", "user"=>{"email"=>"test11#example.com", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Log in"}
User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["email", "test11#example.com"], ["LIMIT", 1]]
(5.0ms) BEGIN
User Exists (3.0ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 AND ("users"."id" != $2) LIMIT $3 [["email", "test11#example.com"], ["id", 23], ["LIMIT", 1]]
Sector Load (0.0ms) SELECT "sectors".* FROM "sectors" INNER JOIN "sectors_users" ON "sectors"."id" = "sectors_users"."sector_id" WHERE "sectors_users"."user_id" = $1 [["user_id", 23]]
Region Load (0.0ms) SELECT "regions".* FROM "regions" INNER JOIN "regions_users" ON "regions"."id" = "regions_users"."region_id" WHERE "regions_users"."user_id" = $1 [["user_id", 23]]
Criterium Load (0.0ms) SELECT "criteria".* FROM "criteria" INNER JOIN "criteria_users" ON "criteria"."id" = "criteria_users"."criterium_id" WHERE "criteria_users"."user_id" = $1 [["user_id", 23]]
AssetType Load (0.0ms) SELECT "asset_types".* FROM "asset_types" INNER JOIN "asset_types_users" ON "asset_types"."id" = "asset_types_users"."asset_type_id" WHERE "asset_types_users"."user_id" = $1 [["user_id", 23]]
Company Load (1.0ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = $1 LIMIT $2 [["id", 42], ["LIMIT", 1]]
(5.0ms) ROLLBACK
############### /users/23/edit
Rendering users/sessions/new.haml within layouts/application
Rendered users/shared/_links.html.erb (3.0ms)
Rendered users/sessions/new.haml within layouts/application (251.2ms)
Rendered layouts/_footer.haml (15.0ms)
Completed 200 OK in 6554ms (Views: 3364.9ms | ActiveRecord: 86.1ms)
Notice it is rendering users/sessions/new.haml instead of the edit page?
Code
class ApplicationController < ActionController::Base
...
def after_sign_in_path_for(resource)
logger.debug '############### ' + edit_user_path(resource) if resource.is_a?(User) && resource.signature.blank?
return edit_user_path resource if resource.is_a?(User) && resource.signature.blank?
stored_location_for(resource) ||
if resource.is_a?(User)
dashboard_path
elsif resource.is_a?(Facilitator) && resource.name.nil?
edit_facilitator_path resource
elsif resource.is_a?(Facilitator)
facilitator_path resource
else
super
end
end
I completely commented out the method and it still reloaded the login page.
Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 22:25:21 -0500
...
Rendering users/sessions/new.haml within layouts/application
Devise 4.4.0
Documentation:
https://github.com/plataformatec/devise/wiki/How-To%3A-Redirect-to-a-specific-page-on-successful-sign-in-and-sign-out
http://www.rubydoc.info/github/plataformatec/devise/master/Devise/Controllers/Helpers:after_sign_in_path_for
I added
def after_sign_in_path_for(resource)
logger.debug '############# ' + resource.errors.full_messages.join(', ')
And did discover validation errors like
############# Title can't be blank, Country can't be blank, Signature can't be blank, ...
But it does show the notice
Signed in successfully.
And I do have a session and can navigate elsewhere. My validations are on: :update.
validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true, on: :update
This should not cause log in behavior errors.
I commented all validations on the model and it does work, but this is highly unusual! Validations should not affect login behavior. There has to be a workaround.
Started POST "/users/sign_in" for 127.0.0.1 at 2018-03-05 23:11:43 -0500
SQL (15.0ms) UPDATE "users" SET "current_sign_in_at" = $1, "last_sign_in_at" = $2, "current_sign_in_ip" = $3, "sign_in_count" = $4, "updated_at" = $5 WHERE "users"."id" = $6 [["current_sign_in_at", "2018-03-06 04:11:44.225501"], ["last_sign_in_at", "2017-11-09 01:22:28.245231"], ["current_sign_in_ip", "127.0.0.1/32"], ["sign_in_count", 6], ["updated_at", "2018-03-06 04:11:44.230506"], ["id", 23]]
Redirected to http://localhost:3000/users/23/edit
Completed 302 Found in 2183ms (ActiveRecord: 48.0ms)
As you only want your validations on update, I guess that you only need them for a specific form, since your users are still valid even without this validations.
In that case I would use a so called form object, that does the on update validations for you and remove the on update validations on your user model. In that case your validations don't affect other parts of your app.
Here is a good guide on how to do that with just using ActiveModel.
Example:
app/models/user.rb
class User < ApplicationRecord
# remove the validations here
end
app/forms/user_edit_form.rb
class UserEditForm
include ActiveModel::Model
ATTRIBUTES = :email, :name, :title, :phone,
:address1, :city, :state, :zip,
:country, :type, :signature
attr_accessor *ATTRIBUTES
validates *ATTRIBUTES, presence: true
def update(user)
if valid?
user.update(self.attributes)
end
end
def self.for_user(user)
new(user.slice(*ATTRIBUTES)
end
end
users_controller.rb
class UsersController
def edit
#user = User.find(params[:id])
#user_edit_form = UserEditForm.for_user(#user)
end
def update
#user = User.find(params[:id])
#user_edit_form = UserEditForm.new(user_update_params).update(#user)
if #user_edit_form.errors?
render :edit
else
redirect_to user_path(#user)
end
end
def user_update_params
# ...
end
end
edit.html.erb
<%= form_for #user_edit_form, url: user_path(#user), method: :patch do |f| %>
# ...
<%= f.submit %>
<% end %>
Alternative
An alternative could be to add a virtual attribute to the model and run your validations conditionally in the user controller.
class User < ApplicationRecord
attr_accessor :profile_complete
with_options if: -> { profile_complete } do
validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true
end
end
users_controller.rb
class UsersController < ApplicationController
def update
#user = User.find(params[:id])
#user.profile_complete = true
if #user.update(user_update_params)
redirect_to #user
else
render :edit
end
# ...
end
end
Note: Instead of using a virtual attribute (attr_accessor) you could also use a real DB attribute, so you can also actually know which users filled out their profile completely.
Alternative 2
In some other projects I also used state machine gems (there are a couple e.g. aasm or statemachines-activerecord) to do somehing similar. Some of the state machine gems even support having validations only for certain states or transisions.
Check this documentation https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in. They have clearly mentioned when you will go in loop and solution for it. Check Preventing redirect loops section in above doc.
You might need conditional validation on your model. Something like this:
validates :email, :name, :title, :phone, :address1, :city, :state, :zip, :country, :type, :signature, presence: true, on: :update, unless: Proc.new {|user| user.current_sign_in_at.present? }
Devise will update sign_in_at whenever sign_in happens. Which will trigger update action and related validations.
Also Documentation said the allow_nil: true instruct the model to validate the fields ONLY if it exists on the submitted form.
I have made a github repo that you can find here just for this question. I have 3 models:
class User < ActiveRecord::Base
has_many :user_countries
has_many :event_countries,
-> { where(user_countries: {:event => true}) },
:through => :user_countries,
:source => :country
has_many :research_countries,
-> { where(user_countries: {:research => true}) },
:through => :user_countries
:source => :country
end
class UserCountry < ActiveRecord::Base
belongs_to :country
belongs_to :user
end
class Country < ActiveRecord::Base
# ...
end
So a user should be able to choose event_countries and research_countries.
here's my users controller (nothing complicated):
class UsersController < ApplicationController
respond_to :html, :json
before_action :get_user, only: [:show, :edit, :update]
before_action :get_users, only: [:index]
def index
end
def show
end
def edit
end
def update
#user.update_attributes(user_params)
respond_with #user
end
private
def get_user
#user = User.find(params[:id])
end
def get_users
#users = User.all
end
def user_params
params.require(:user).permit(:first_name, :event_countries => [:id, :name])
end
end
And here's my user show page:
<%= best_in_place #user, :first_name %>
<p> event countries: </p>
<%= best_in_place #user, :event_countries, place_holder: "click here to edit", as: :select, collection: Country.all.map {|i| i.name} %>
<%= link_to "users index", users_path %>
So there's really nothing complicated here. I can also succesfully edit my users first name, best_in_place is working fine.
The question is: how do I edit the event_countries ? As you can see I tried to use the collection option with the countries but when I try to select a country I get the following:
Processing by UsersController#update as JSON
Parameters: {"user"=>{"event_countries"=>"3"}, "authenticity_token"=>"l5L5lXFmJFQ9kI/4klMmb5jDhjmtQXwn6amj1uwjSuE=", "id"=>"6"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 6]]
(0.1ms) begin transaction
(0.1ms) rollback transaction
Completed 500 Internal Server Error in 3ms
NoMethodError (undefined method `each' for nil:NilClass):
app/controllers/users_controller.rb:17:in `update'
I don't understand what it's doing, I know it must be a problem with the collection option. If you need to see any file please check my repo here:
https://github.com/spawnge/best_in_place_join_models_twice . I have a spent a lot of time on this any answer/suggestion would be greatly appreciated :)
update:
I have tried this:
<%= best_in_place #user, :event_country_ids, as: :select, collection: Country.all.map { |i| i.name }, place_holder: "click here to edit", html_attrs: { multiple: true } %>
and I have added :event_country_ids to my user params:
params.require(:user).permit(:first_name, :event_country_ids)
And now I can see all the countries but when I select one here's what I get:
Started PUT "/users/3" for 127.0.0.1 at 2014-12-18 01:19:27 +0000
Processing by UsersController#update as JSON
Parameters: {"user"=>{"event_country_ids"=>"1"}, "authenticity_token"=>"aZAFIHgzdSL2tlFcGtyuu+XIJW3HX2fwQGHcB9+iYpI=", "id"=>"3"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 3]]
(0.0ms) begin transaction
Country Load (0.0ms) SELECT "countries".* FROM "countries" WHERE "countries"."id" = ? LIMIT 1 [["id", 1]]
Country Load (0.0ms) SELECT "countries".* FROM "countries" INNER JOIN "user_countries" ON "countries"."id" = "user_countries"."country_id" WHERE "user_countries"."event" = 't' AND "user_countries"."user_id" = ? [["user_id", 3]]
SQL (0.1ms) DELETE FROM "user_countries" WHERE "user_countries"."user_id" = ? AND "user_countries"."country_id" = 2 [["user_id", 3]]
SQL (0.0ms) INSERT INTO "user_countries" ("country_id", "event", "user_id") VALUES (?, ?, ?) [["country_id", 1], ["event", "t"], ["user_id", 3]]
(20.9ms) commit transaction
Completed 204 No Content in 28ms (ActiveRecord: 21.3ms)
As you can see it seems that it insert the right content: INSERT INTO "user_countries" ("country_id", "event", "user_id") VALUES (?, ?, ?) [["country_id", 1], ["event", "t"], ["user_id", 3]] However I get the Completed 204 No Content just after that. I don't understand when I refresh the page the input is empty. Any suggestion ?
Update 2:
I checked in the console and it works, I can add event_countries to a user. However it doesn't display the user's event_countries when I refresh the page, I guess that's because I'm using the event_country_ids object.
I think the following code should work:
<%= best_in_place #user, :event_country_ids, as: :select,
collection: Country.all.each_with_object({}) { |i, memo| memo[i.id] = i.name },
place_holder: "click here to edit",
html_attrs: { multiple: true } %>
Assuming you want the user to be able to assign multiple event_countries.
Reference
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many, specifically the collection_singular_ids= method created by has_many.
https://github.com/bernat/best_in_place#select, the structure of the collection needs to be a hash. For each key => value pair, the key is what's submitted with the form and the value is what's displayed to the user.
http://ruby-doc.org/core-2.1.5/Enumerable.html#method-i-each_with_object, each_with_object is a part of the core Ruby library.
In my app, I give access to a customer to HIS own deals thanks to Cancan.
It works when I try it "manually" with the browser but I fail at implementing the rspec tests. A customer can't access other customer's deals but only his own (the administrator give him access through Active Admin interface).
It's like I am not managing to make rspec understand that the customer(through FactoryGirl) I create for tests should be allowed/associated with the deals I create for the tests (again through FactoryGirl).
THE TEST: /spec/controllers/deals_controller_spec.rb
require 'spec_helper'
require "cancan/matchers"
describe DealsController do
context "As signed-in CUSTOMER" do
before do
#customer = FactoryGirl.create(:customer) #the factory builds a basic customer i.e with 'prospect role' attributed by default
#deal = FactoryGirl.create(:deal, :customers => [#customer]) # in array as a deal has_many customers
sign_in_customer #customer
end
describe "the customer can read=view the page of a Deal HE OWNS " do
it "can access the page" do
get :deal_page, { :id => #deal.id }
expect(current_path).to eq(deal_page_path(#deal))
page.should have_content('Here is one of your deals, dear customer')
end
end
end
Here is the error I get:
DealsController As signed-in CUSTOMER with access to the deal page
Failure/Error: expect(current_path).to eq(deal_page_path(#deal))
expected: "/deals_page/2"
got: "/customer_interface_homepage"
(compared using ==)
Here is the detailed test log
Deal Exists (0.8ms) SELECT 1 AS one FROM "deals" WHERE LOWER("deals"."deal_code") = LOWER('CHA1FR001') LIMIT 1
SQL (2.1ms) INSERT INTO "deals" ("admin_user_id", "client_contact_point_name", blabla") VALUES ($1, $2, blabla...) RETURNING "id" [["admin_user_id", 1], ["client_contact_point_name", "henri Cool"], ["client_contact_point_profile_url", "http://example.com"], ....blabla...]
(...blabla)
Customer Exists (0.6ms) SELECT 1 AS one FROM "customers" WHERE (LOWER("customers"."email") = LOWER('person_1#example.com') AND "customers"."id" != 1) LIMIT 1
(...blabla)
Started GET "/customers/signin" for 127.0.0.1 at 2014-05-28 18:37:05 +0200
Processing by Customers::SessionsController#new as HTML
Rendered customers/sessions/new.html.erb within layouts/lightbox (40.0ms)
Rendered layouts/_metas.html.erb (0.4ms)
Rendered layouts/_messages.html.erb (0.7ms)
Rendered layouts/_footer.html.erb (1.2ms)
Completed 200 OK in 77ms (Views: 51.5ms | ActiveRecord: 0.0ms)
Started POST "/customers/signin" for 127.0.0.1 at 2014-05-28 18:37:05 +0200
Processing by Customers::SessionsController#create as HTML
Parameters: {"utf8"=>"✓", "customer"=>{"email"=>"person_1#example.com", "password"=>"[FILTERED]"}, "commit"=>"Log In"}
Customer Load (4.0ms) SELECT "customers".* FROM "customers" WHERE "customers"."email" = 'person_1#example.com' ORDER BY "customers"."id" ASC LIMIT 1
SQL (1.0ms) UPDATE "customers" SET "remember_created_at" = $1, "updated_at" = $2 WHERE "customers"."id" = 1 [["remember_created_at", 2014-05-28 16:37:05 UTC], ["updated_at", 2014-05-28 18:37:05 +0200]]
SQL (1.2ms) UPDATE "customers" SET "last_sign_in_at" = $1, "current_sign_in_at" = $2, "last_sign_in_ip" = $3, "current_sign_in_ip" = $4, "sign_in_count" = $5, "updated_at" = $6 WHERE "customers"."id" = 1 [["last_sign_in_at", 2014-05-28 16:37:05 UTC], ["current_sign_in_at", 2014-05-28 16:37:05 UTC], ["last_sign_in_ip", "127.0.0.1"], ["current_sign_in_ip", "127.0.0.1"], ["sign_in_count", 1], ["updated_at", 2014-05-28 18:37:05 +0200]]
**Redirected to http://www.example.com/customer_interface_homepage**
Completed 302 Found in 33ms (ActiveRecord: 6.2ms)
Started GET "/customer_interface_homepage" for 127.0.0.1 at 2014-05-28 18:37:05 +0200
Processing by ClientreportingPagesController#index as HTML
Customer Load (0.5ms) SELECT "customers".* FROM "customers" WHERE "customers"."id" = 1 ORDER BY "customers"."id" ASC LIMIT 1
(1.2ms) SELECT COUNT(*) FROM "roles" INNER JOIN "customers_roles" ON "roles"."id" = "customers_roles"."role_id" WHERE "customers_roles"."customer_id" = $1 AND (((roles.name = 'prospect') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["customer_id", 1]]
Rendered layouts/_metas.html.erb (0.2ms)
(0.8ms) SELECT COUNT(*) FROM "roles" INNER JOIN "customers_roles" ON "roles"."id" = "customers_roles"."role_id" WHERE "customers_roles"."customer_id" = $1 AND (((roles.name = 'superadmin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["customer_id", 1]]
Rendered layouts/client_interface_partials
Completed 200 OK in 34ms (Views: 27.7ms | ActiveRecord: 2.4ms)
Processing by DealsController#deal_page as HTML
Parameters: {"id"=>"2"}
**Completed 401 Unauthorized in 1ms**
Rendered text template (0.1ms)
(0.5ms) ROLLBACK TO SAVEPOINT active_record_2
(0.3ms) ROLLBACK TO SAVEPOINT active_record_1
(0.3ms) ROLLBACK
I'm not sure it's the root cause of the issue but 2 things seem strange to me in this log:
why does rspec send to example.com/customer_interface_homepage (i have in my spec_helper file told rspec that i test locally: Capybara.asset_host = 'http:// localhost:3000') ?
why does rspec experience a "Completed 401 Unauthorized in 1ms at the end ?
Some files that might be useful to solve the issue:
/app/models/customer_ability.rb
class CustomerAbility
include CanCan::Ability
def initialize(customer)
alias_action :show, :to => :read #this will have no change on the alias :read!
customer ||= Customer.new # guest customer (not logged in)
if customer.has_role? :superadmin
Log.info "Ability: customer is superadmin"
can :manage, :all
else
can :read, Deal do |deal|
# Only customers who have been granted access in Active Admin to a deal can read
deal.customers.include? customer
end
end
end
end
controllers/deals_controller.rb
class DealsController < ApplicationController
before_filter :authenticate_customer!,
:only => [ :deal_page ]
def deal_page
#deal = Deal.find(params[:id])
authorize! :read, #deal # only allow customers with authorized access in AA; sends to customer_ability
respond_to do |format|
format.html
format.json { render json: #deal }
end
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
# handle Cancan authorization exception
rescue_from CanCan::AccessDenied do |exception|
exception.default_message = t("errors.application_controller_exception_messages.only_open_to_admin")
if current_user # if it's user redirect to main HP
redirect_to root_path, :alert => exception.message
else # if it's a Customer redirect him to client interface HP
redirect_to customer_interface_homepage_path, :alert=> exception.message
end
end
def current_ability #inspired by http://mikepackdev.com/blog_posts/12-managing-devise-s-current-user-current-admin-and-current-troll-with-cancan
#current_ability ||= case
when current_user
UserAbility.new(current_user)
when current_customer
CustomerAbility.new(current_customer)
end
end
/spec/support/utilities.rb
include ApplicationHelper
def sign_in_customer(customer)
customer.confirm!
visit new_customer_session_path
fill_in "Email", with: customer.email
fill_in "Password", with: customer.password
click_on "Log In"
#populate cookie when not using capybara
cookies[:authentication_token] = customer.authentication_token
end
/spec/factories/deals.rb
FactoryGirl.define do
factory :deal do
# id i don't here any id
sequence(:deal_campaign_code) { |n| "CHA#{n}FR001" }
featured true
admin_user_id 1
end
end
/spec/factories/customers.rb
FactoryGirl.define do
factory :customer do # we use prospect as by definition a visitor signing in gets 'prospect status'
sequence(:email) { |n| "person_#{n}#example.com"}
password "bet(8a3#"
password_confirmation "bet(8a3#"
# required if the Devise Confirmable module is used
confirmed_at Time.now
confirmation_token nil
# create deals connected to the Customer
after(:create) do |customer|
customer.deals << FactoryGirl.create(:deal)
end
end
end
/app/models/customer.rb
class Customer < ActiveRecord::Base
rolify
# -- Relationships --------------------------------------------------------
has_many :customer_deals, dependent: :destroy
has_many :deals, through: :customer_deals
/app/models/deal.rb
class Deal < ActiveRecord::Base
# -- Relationships --------------------------------------------------------
belongs_to :admin_user, :foreign_key => 'admin_user_id'
has_many :customer_deals, dependent: :destroy
has_many :customers, through: :customer_deals
/app/models/customer_deal.rb
class CustomerDeal < ActiveRecord::Base
# -- Relationships --------------------------------------------------------
belongs_to :customer, :foreign_key => 'customer_id'
belongs_to :deal, :foreign_key => 'deal_id'
I think the session information is not being passed into the request you make in the spec. See this guide for how to use devise with controller tests.
As an alternative approach, I would recommend making this a feature spec instead of a controller spec. Note to make capybara play nice with devise follow this guide.
To answer your log questions:
Not sure why it says example.com/customer_interface_homepage but you can see on the next line that it is actually sending the GET request to 127.0.0.1/customer_interface_homepage. So that part is working. Perhaps your hosts file has example.com pointing to 127.0.0.1 and the first message is resolving the IP with DNS?
The 401 unauthorized looks to be due to your deal controller's authorize! call.
The spec is almost certainly failing due to current_path not being updated because of the 401 return from the deals controller. I believe if you fix that issue the current_path issue will also be fixed.
I have had problems in the past with FactoryGirl not actually writing models to the database on creation. This will cause anything relying on associations (which authorize! looks like it probably relies on due to CustomerAbility) to fail.
My suggestions moving forward are to debug authorize! (likely using log/puts statements) and figuring out what #deal actually looks like at that point as well as what deal.customers looks like. If you post the code to authorize! we may be able to help more.
I have Subcategories which belong to Categories. My app saves (build) all my Subcategories under Subcategory with id = 1, even though my code seems to be ok and it is not supposed to do that:
Subcategories controller:
def create
#category = Category.find_by(params[:id])
#subcategory = #category.subcategories.build(subcategory_params)
if #subcategory.save
flash[:success] = "added subcategory"
redirect_to admin_categories_url
else
render :new
end
end
...
private
def subcategory_params
params.require(:subcategory).permit(:name, :category_id)
end
Subcategory.rb
class Subcategory < ActiveRecord::Base
belongs_to :category
has_many :products
validates :name, presence: true
validates :category_id, presence: true
end
Form:
<h3>Add a subcategory</h3>
<%= form_for [#category, #subcategory] do |f| %>
<%= f.text_field :name, placeholder: "Name" %>
<%= f.submit "Add a subcategory" %>
<% end %>
router:
resources :categories do
resources :subcategories
end
URL:
Adding a new subcategory to category with id = 3
http://localhost:3000/categories/3/subcategories/new
Logs:
Started POST "/categories/1/subcategories" for 127.0.0.1 at 2014-04-16 16:04:47 +0400
Processing by SubcategoriesController#create as HTML
Parameters: {"utf8"=>"✓", authenticity_token"=>"74U3VyAN6NqEjhkuHGNHnPda/yzpc+dIcn2xBJ6Zi2A=", "subcategory"=>{"name"=>"A name"}, "commit"=>"Add subcategory", "category_id"=>"1"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Category Load (0.2ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1 [["id", 1]]
(0.1ms) BEGIN
SQL (0.2ms) INSERT INTO "subcategories" ("category_id", "created_at", "name", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["category_id", 1], ["created_at", "2014-04-16 12:04:47.151729"], ["name", "A name"], ["updated_at", "2014-04-16 12:04:47.151729"]]
(0.8ms) COMMIT
Id is 1
What can be the problem here?
Update
When doing
#category = Category.find_by(params[:category_id])
this error comes up:
ERROR: argument of WHERE must be type boolean, not type integer
LINE 1: SELECT "categories".* FROM "categories" WHERE (1) LIMIT 1
If doing
#category = Category.find(params[:category_id])
problem presists
In your create action you are picking up the id of the subcategory, not the category. Change it to:
def create
#category = Category.find(params[:category_id])
...
The same change to setting #category needs to be made in the new method too so that category_id comes back as 3 in the params from the view. Of course this duplication can be also DRYed up using a helper method in the controller.
To see the difference in the id, you can use rake routes and you should see something like:
new_category_subcategory GET /categories/:category_id/subcategories/new subcategories#new
This shows you that it expects the parameter category_id for the id of the category.
Look at your parameters it consist category_id not id
Parameters: {"utf8"=>"✓", authenticity_token"=>"74U3VyAN6NqEjhkuHGNHnPda/yzpc+dIcn2xBJ6Zi2A=", "subcategory"=>{"name"=>"A name"}, "commit"=>"Add subcategory", "category_id"=>"1"}
#=> "category_id"=>"1"
Category.find_by(category_id: params[:category_id])
So you need to do
def create
#category = Category.find_by(category_id: params[:category_id])
#subcategory = #category.subcategories.build(subcategory_params)
if #subcategory.save
flash[:success] = "added subcategory"
redirect_to admin_categories_url
else
render :new
end
end
I have public_activity working on my rails app for the guidelines model. But there is a problem with deleting a guideline. Updating and creating guidelines are fine.
The error says
ActiveRecord::RecordNotSaved (You cannot call create unless the parent is saved):
app/controllers/guidelines_controller.rb:228:in `destroy'
guidelines_controller.rb
def destroy
#guideline = Guideline.find(params[:id])
#guideline.destroy
#guideline.create_activity :destroy, owner: current_user
respond_to do |format|
format.html { redirect_to guidelines_url }
format.json { head :no_content }
end
end
def update
#guideline = Guideline.find(params[:id])
if #guideline.update_attributes(params[:guideline])
#guideline.create_activity :update, owner: current_user
end
def create
#guideline = current_user.guidelines.new(params[:guideline])
if #guideline.save
#guideline.create_activity :create, owner: current_user
end
guideline.rb
include PublicActivity::Common
view public_activity/guideline/_destroy.html.erb
deleted a guideline
<% if activity.trackable %>
<%= link_to activity.trackable.title, activity.trackable %>
<% else %>
which can no longer be viewed
<% end %>
the rails log says
Processing by GuidelinesController#destroy as HTML
Parameters: {"authenticity_token"=>"SpdYUFk0Bv1KuVg6oEuDUU4MI3eD6C1nV/3bmd5Xhsg=", "id"=>"9-jannit"}
Guideline Load (0.2ms) SELECT "guidelines".* FROM "guidelines" WHERE "guidelines"."id" = ? LIMIT 1 [["id", "9-jannit"]]
(0.1ms) begin transaction
Comment Load (0.3ms) SELECT "comments".* FROM "comments" WHERE "comments"."guideline_id" = 9
SQL (0.7ms) DELETE FROM "guidelines" WHERE "guidelines"."id" = ? [["id", 9]]
SOLR Request (224.9ms) [ path=#<RSolr::Client:0x007f9ebdc5ea50> parameters={data: <?xml version="1.0" encoding="UTF-8"?><delete><id>Guideline 9</id></delete>, headers: {"Content-Type"=>"text/xml"}, method: post, params: {:wt=>:ruby}, query: wt=ruby, path: update, uri: http://localhost:8982/solr/update?wt=ruby, open_timeout: , read_timeout: } ]
(3.3ms) commit transaction
(0.1ms) begin transaction
SOLR Request (4.8ms) [ path=#<RSolr::Client:0x007f9ebdc5ea50> parameters={data: <?xml version="1.0" encoding="UTF-8"?><delete><id>Guideline 9</id></delete>, headers: {"Content-Type"=>"text/xml"}, method: post, params: {:wt=>:ruby}, query: wt=ruby, path: update, uri: http://localhost:8982/solr/update?wt=ruby, open_timeout: , read_timeout: } ]
(0.1ms) commit transaction
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
Completed 422 Unprocessable Entity in 254ms
Perhaps you could try setting :autosave => true in your association. For instance:
class User < ActiveRecord::Base # or wherever location the guidelines association is being set
has_many :guidelines, :autosave => true
...
end