rails test not allowing to assert processing of before_action - ruby-on-rails

The following unit test succeeds in the first assertion, but fails in the second
test "login_name, kee and virtual_qr changed" do
#user = users(:funky_mobile)
assert_changes 'users(:funky_mobile).mobile' do
#user.update(address: 'muuvt', mobile: '728363', mobile_nation_id: 1)
# patch update_user_path(#user_mobile), params: { user: { address: 'muuvt', mobile: '728364', mobile_nation_id: 1 } }
end
assert_changes 'users(:funky_mobile).login_name' do
#user.update(address: 'muuvt', mobile: '728365', mobile_nation_id: 1)
end
end
"users(:funky_mobile).login_name" didn't change.
Note: while patch - attempted as per the following - does process correctly, it appears not applicable to a unit test.
The unit test with update only registers the submitted params but does not kick in the before_action, as per test.log
User Update (0.5ms)[0m [1m[33mUPDATE "users" SET "mobile" = $1 WHERE "users"."id" = $2[0m [["mobile", 728365], ["id", 165956397]]
Conversely, with an integration test
sign_in users(:funky_mobile)
assert_changes 'users(:funky_mobile).login_name' do
patch user_url(#user_mobile), params: { user: { address: 'muuvt', mobile: '728364', mobile_nation_id: 1 } }
end
"users(:funky_mobile).login_name" didn't change.
Expected nil to not be equal to nil.
the changed data enacted via before_action :set_user_login_name, only: %i[ update ] of the users_controller
def set_user_login_name
if params[:user][:email].present?
params[:user][:login_name] = params[:user][:email].gsub(/\s+/, "")
elsif params[:user][:mobile_nation_id].present? && params[:user][:mobile].present?
#nation = Nation.where(id: params[:user][:mobile_nation_id]).first
params[:user][:login_name] = #nation.phone_cc.to_s + params[:user][:mobile].to_s
params[:user][:twilio_number] = '+' + #nation.phone_cc.to_s + params[:user][:mobile].to_s
else
params[:user][:login_name]
end
params[:user][:kee] = SecureRandom.alphanumeric(32)
params[:user][:virtual_qr_code] = params[:user][:login_name] + params[:user][:kee]
end
is processed as demonstrated by the test.log
User Update (0.7ms)[0m [1m[33mUPDATE "users" SET "login_name" = $1, "kee" = $2, "virtual_qr_code" = $3 WHERE "users"."id" = $4[0m [["login_name", "39728364"], ["kee", "Rce8sQoH0VPvqUPeuegZL4gYVmJuamqi"], ["virtual_qr_code", "39728364Rce8sQoH0VPvqUPeuegZL4gYVmJuamqi"], ["id", 165956397]]
TRANSACTION (0.1ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
Redirected to http://www.example.com/users/165956397
Oddly, if the integration test is altered reomving assert_changes to simply
patch user_url(#user_mobile), params: { user: { address: 'muuvt', mobile: '728364', mobile_nation_id: 1 } }
puts #user_mobile.mobile
assert_equal(39728364, #user_mobile.mobile)
returns
3331112200
F [...]
Expected: 39728364
Actual: 3331112200
yet the log shows the same User update reference as above.
How can I properly assert that these changes are being carried forth?

This is going to fail...
assert_changes 'users(:funky_mobile).login_name' do
because...
users(:funky_mobile).login_name
returns the original value every time. It is calling the fixture and acts like a constant.
What you should do is...
login_name = users(:funky_mobile).login_name
assert_changes login_name do

Related

WARN: NameError: uninitialized constant DeliveryMethods when moving code to ActiveJob

I have code to send invites to members of my website and it can be sent via email, real-time notification or One Signal. The code works great in development until I move the invite to an ActiveJob to be processed in the background using Sidekiq and Redis. I am doing this only for when a maintainer of an organization uploads a CSV file of contacts to invite to their organization. (Thus the background job as some clients wish to invite around 10,000+ people which would bog the system if done within the controller.)
If I move the task to an ActiveJob, I get this error in the Sidekiq output:
WARN: NameError: uninitialized constant DeliveryMethods
I thought that this was because I didn't put a require statement in the ActiveJob, so I added this to the top of the ActiveJob:
require 'application_notification'
But, I get the same error message.
I am at a loss. Any help would be greatly appreciated. Here are code snippets. Please let me know if you need anything else.
Versions
Ruby:'3.0.2'
Rails: 7.0.0.alpha
gem 'rails', :github => 'rails/rails', :branch => 'main'
Redis: '~> 4.1.3'
Sidekiq: '6.0.7'
Result Output
# Terminal Output
Started POST "/import_wizard/organization/1" for ::1 at 2021-08-10 16:47:30 -0700
Processing by InvitationsController#invite_imports as JS
Parameters: {"authenticity_token"=>"--REDACTED--", "invitable_type"=>"organization", "invitable_id"=>"1"}
Member Load (1.1ms) SELECT "members".* FROM "members" WHERE "members"."id" = $1 ORDER BY "members"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/concerns/cookies_concern.rb:171:in `load_cookies'
Organization Load (1.3ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/invitations_controller.rb:216:in `set_invitable'
ImportResult Load (0.8ms) SELECT "import_results".* FROM "import_results" WHERE "import_results"."invitable_id" = $1 AND "import_results"."status" = $2 LIMIT $3 [["invitable_id", 1], ["status", 1], ["LIMIT", 1]]
↳ app/controllers/invitations_controller.rb:261:in `set_imports_to_invite'
ImportRecord Load (0.7ms) SELECT "import_records".* FROM "import_records" WHERE "import_records"."import_result_id" = $1 AND "import_records"."status" = $2 [["import_result_id", 32], ["status", "ready"]]
↳ app/controllers/invitations_controller.rb:155:in `invite_imports'
[ActiveJob] Enqueued InviteImportedMembersJob (Job ID: 69355585-cfef-4f1f-bf90-eae0f24d5f98) to Sidekiq(imports) with arguments:
#<GlobalID:0x00007fbeba81d0a0 #uri=#<URI::GID gid://prayer-nook/Organization/1>>,
[#<GlobalID:0x00007fbeba81c6a0 #uri=#<URI::GID gid://prayer-nook/ImportRecord/309>>,
#<GlobalID:0x00007fbeba817d08 #uri=#<URI::GID gid://prayer-nook/ImportRecord/310>>,
#<GlobalID:0x00007fbeba817470 #uri=#<URI::GID gid://prayer-nook/ImportRecord/311>>,
#<GlobalID:0x00007fbeba816d68 #uri=#<URI::GID gid://prayer-nook/ImportRecord/312>>,
#<GlobalID:0x00007fbeba816250 #uri=#<URI::GID gid://prayer-nook/ImportRecord/313>>,
#<GlobalID:0x00007fbeba8157b0 #uri=#<URI::GID gid://prayer-nook/ImportRecord/318>>,
#<GlobalID:0x00007fbeba814a40 #uri=#<URI::GID gid://prayer-nook/ImportRecord/319>>],
#<GlobalID:0x00007fbeb9a5f198 #uri=#<URI::GID gid://prayer-nook/Member/1>>
Rendering invitations/invite_imports.js.erb
Rendered invitations/invite_imports.js.erb (Duration: 0.1ms | Allocations: 10)
Completed 200 OK in 317ms (Views: 3.4ms | ActiveRecord: 95.8ms | Allocations: 57969)
Controller Action
The commented out line for invite_imports_task is a method I made within the controller with the exact same code that runs in the ActiveJob, but works. So, I know that the code works, its just moving to an ActiveJob that is now causing the issue.
# InvitationsController#invite_imports
# app/controllers/invitations_controller.rb
def invite_imports
set_invitable
set_imports_to_invite
#import_step = 4
imports_to_invite_array = []
#imports_to_invite.each do |record|
imports_to_invite_array << record
end
InviteImportedMembersJob.perform_later(#invitable, imports_to_invite_array, #authenticated_member)
# invite_imports_task(#invitable, imports_to_invite_array, #authenticated_member)
respond_to do |format|
format.js
end
end
Active Job
# app/jobs/invite_imported_members_job.rb
class InviteImportedMembersJob < ApplicationJob
require 'application_notification'
queue_as :imports
def perform(invitable, imports_to_invite, sender)
set_import_result(invitable)
imported_emails = imports_to_invite.map {|member| member[:email]}
member_list = Member.where(email: imported_emails)
member_email_list = member_list.pluck(:email)
non_member_email_list = imported_emails - member_email_list
sent_invites = []
error_in_sending_invites = []
member_list.each do |member|
invitation = Invitation.new(invitable: invitable, sender: sender, recipient:member)
if invitation.save
invitable.invited_members << member
sent_invites << member.email
else
error_in_sending_invites << member.email
end
end
non_member_email_list.each do |member|
InvitationMailer.with(recipient_email: member, sender: sender).app_invitation.deliver_later
waitlist = InvitationWaitlist.create(email: member, invitable: invitable, sender: sender)
# in this case the member variable is only an email address
if waitlist.save
sent_invites << member
else
error_in_sending_invites << member
end
end
update_import_records(invitable, sent_invites, error_in_sending_invites)
update_import_result
create_cue_notification(invitable)
end
private
def set_import_result(invitable)
#import_result = ImportResult.find_by(invitable:invitable, status: 'waiting')
end
def update_import_records(invitable, sent_invites, error_in_sending_invites)
if sent_invites.count > 0
ImportRecord.where(import_result_id:#import_result.id, email: sent_invites).update_all(status:'sent')
end
if error_in_sending_invites.count > 0
ImportRecord.where(import_result_id:#import_result.id, email: error_in_sending_invites).update_all(status:'error_in_sending')
end
end
def update_import_result
#import_result.completed!
end
def create_cue_notification(invitable)
hide_old_cues(invitable)
CueService.new(#import_result, set_cue_recipients(invitable), false).call!
end
def hide_old_cues(invitable)
Cue.where(cueable: #import_result).update(status:'hidden')
end
def set_cue_recipients(invitable)
if invitable.is_a?(Organization)
return invitable.maintainers
elsif invitable.is_a?(Group)
return invitable.owner
else
return nil
end
end
end
Application Notification
# app/notifications/application_notification.rb
class ApplicationNotification < Noticed::Base
deliver_by :database, format: :format_for_database
deliver_by :action_cable, channel: 'NotificationsChannel', format: :format_for_action_cable
deliver_by :one_signal, class: "DeliveryMethods::OneSignal", format: :format_for_one_signal
def format_for_database
{
type: self.class.name,
params: params
}
end
end
DeliveryMethod::OneSignal
# app/notifications/delivery_methods/one_signal.rb
class DeliveryMethods::OneSignal < Noticed::DeliveryMethods::Base
def deliver
return unless app_id.present? && one_signal_url.present? && player_id.present?
params = {"app_id" => app_id,
"contents" => {"en" => message},
"headings" => {"en" => "Prayer Nook"},
"include_player_ids" => [player_id],
"data" => data
}
uri = URI.parse(one_signal_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path,'Content-Type' => 'application/json;charset=utf-8')
request.body = params.as_json.to_json
response = http.request(request)
puts "OneSignal response: #{response.body}"
end
private
def app_id
ENV['ONE_SIGNAL_APP_ID']
end
def one_signal_url
ENV['ONE_SIGNAL_API_URL']
end
def player_id
recipient.site_profile.one_signal_id
end
def message
if (method = options[:format])
notification.send(method)[:message]
else
"Message from Prayer Nook"
end
end
def data
if (method = options[:format])
notification.send(method)[:data]
else
{ }
end
end
end
From the Invitation Model
## app/models/invitation.rb
def send_notifications
if self.invitable_type == 'Group'
GroupInvitationNotification.with(invitation: self, group: self.invitable, sender: self.sender).deliver_later(self.recipient)
elsif self.invitable_type == 'Organization'
OrgInvitationNotification.with(invitation: self, organization: self.invitable, sender: self.sender).deliver_later(self.recipient)
end
end
OrgInvitationNotification
# app/notifications/org_invitation_notification.rb
class OrgInvitationNotification < ApplicationNotification
# this class inherits other delivery methods from ApplicationNotification: database, action_cable, and one_signal
deliver_by :email, mailer: "InvitationMailer", method: :org_invitation, if: :immediate_email_notifications?
# required params
param :invitation
param :organization
param :sender
# helper methods to make rendering easier.
def format_for_action_cable
html = ApplicationController.render(
partial: 'notifications/toast',
locals: { header: "You've been invited",
message: message,
link_path: invitation_path(params[:invitation])
}
)
params.merge(html: html)
end
def format_for_one_signal
{
message: message,
data: { page: 'invitation', id: params[:invitation].id }
}
end
def immediate_email_notifications?
recipient.site_profile.invitations_email_notifications == 'immediately'
end
def message
t(".message", sender: params[:sender].full_name, org_name: params[:organization].name)
end
def url
invitation_url(params[:invitation])
end
end
Update
New code block per #LamPhan's comments:
# From config/application.rb
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 6.0
config.active_job.queue_adapter = :sidekiq
config.active_record.encryption.support_unencrypted_data = true
config.active_record.legacy_connection_handling = false
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
config.generators do |g|
g.test_framework :rspec,
fixtures: false,
view_specs: false,
helper_specs: false,
routing_specs: false
end
config.autoloader = :classic
end
According to comments, you're using :classic loader and your project're running on Rails 7.0.
Base on this comment (of the creator of sidekiq): Sidekiq does not work with the classic autoloader in Rails 6 at all.
so you should use :zeitwerk loader.

Rails PUT/POST request fails on first try, works on next

I have a VueJS Frontend application with a Ruby On Rails API backend. I need to change information about a user in the database.
I have a PUT/POST request that removes the creditcard information. The basic rails code is below. The issue I am having is that I get the, very popular, error:
NoMethodError (undefined method []' for nil:NilClass):
But the catch is that it only happens on the first attempt. Subsequent attemps are successful. Can someone point me in the right direction here? Should you need additional informaiton, please don't hesitate to ask.
class AccountsController < ApplicationController
before_action :load_account, only: [:show, :update, :destroy, :formats, :charges, :destroy_card]
def destroy_card
#account.card_last_4 = nil
#account.card_type = nil
#account.card_exp_month = nil
#account.card_exp_year = nil
#account.plan_id = 1
#account.save!
render json: { account: #account }, status: 200
end
private
def load_account
if #current_user.admin?
#account = Account.find(params[:id])
else
#account = #current_user.accounts.find_by_id(params[:id]) unless #account.present?
end
end
end
Errors:
Started PUT "//accounts/20/destroy_card" for ::1 at 2020-08-17 20:24:47 -0400
Processing by AccountsController#destroy_card as */*
Parameters: {"id"=>20, "account_id"=>"20"}
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 10 LIMIT 1
↳ app/controllers/application_controller.rb:18:in `load_user'
AccountUser Load (0.2ms) SELECT `account_users`.* FROM `account_users` WHERE `account_users`.`user_id` = 10 AND `account_users`.`account_id` = 20 LIMIT 1
↳ app/controllers/application_controller.rb:28:in `load_user'
Account Load (0.1ms) SELECT `accounts`.* FROM `accounts` WHERE `accounts`.`id` = 20 LIMIT 1
↳ app/controllers/application_controller.rb:30:in `load_user'
CACHE Account Load (0.0ms) SELECT `accounts`.* FROM `accounts` WHERE `accounts`.`id` = 20 LIMIT 1 [["id", 20], ["LIMIT", 1]]
↳ app/controllers/accounts_controller.rb:97:in `load_account'
(0.1ms) BEGIN
↳ app/models/account.rb:148:in `assign_plan'
Plan Load (0.1ms) SELECT `plans`.* FROM `plans` WHERE `plans`.`id` = 1 LIMIT 1
↳ app/models/account.rb:148:in `assign_plan'
Account Update (0.2ms) UPDATE `accounts` SET `accounts`.`card_last_4` = NULL, `accounts`.`card_type` = NULL, `accounts`.`card_exp_month` = NULL, `accounts`.`card_exp_year` = NULL, `accounts`.`plan_id` = 1, `accounts`.`updated_at` = '2020-08-18 00:24:47' WHERE `accounts`.`id` = 20
↳ app/controllers/accounts_controller.rb:82:in `destroy_card'
Account Update (0.3ms) UPDATE `accounts` SET `accounts`.`plan_updated_at` = '2020-08-18 00:24:47' WHERE `accounts`.`id` = 20
↳ app/models/account.rb:60:in `stripe_subscribe'
(9.1ms) ROLLBACK
↳ app/controllers/accounts_controller.rb:82:in `destroy_card'
Completed 500 Internal Server Error in 1150ms (ActiveRecord: 10.4ms | Allocations: 22601)
NoMethodError (undefined method `[]' for nil:NilClass):
app/models/account.rb:71:in `stripe_subscribe'
app/controllers/accounts_controller.rb:82:in `destroy_card'
Code from account.rb
def stripe_subscribe
s = stripe_customer.subscriptions.first
self.plan_updated_at = Time.now
save!
if coupon.present? && plan.id != coupon.plan_id
self.coupon = nil
save!
end
if plan.price == 0
# Downgrading from a paid tier
if s.present?
meta = s.delete
self.changed_plan = Plan.find(previous_changes[:plan_id][0])
self.changed_plan_date = Time.at(meta['current_period_end']).to_datetime
save!
end
else
s.delete({ prorate: true, invoice_now: true }) if s.present?
params = { items: [{ plan: plan.short_name }] }
if coupon.present?
params[:coupon] = coupon.token
end
stripe_customer.subscriptions.create(params)
if Rails.env == 'production'
client = Slack::Web::Client.new
client.chat_postMessage(channel: '#somechannel', text: "some random message", as_user: true)
end
end
end

Rails submit create action succeeding

I am learning ruby rails and i have a #create problem (i think)
when i am creating a new "stat" with form_for, once i press submit i get redirect to index.html.erb (as written in my controller) but there is no data apart from the id (even if i chose my self an id, it get generated by rails)
the weird part is, if i edit the new entry (where everything is blank but :ID) data are saved.
I hope i am clear enough it's my first question on stackoverflow
Thx a lot !
My controller : stats
def new
#stat = Stat.new
end
def create
#stat = Stat.new
if #stat.save
redirect_to "/stats"
flash[:notice] = "work"
else
render "new"
flash[:notice] = "didn't work"
end
end
the log for create action
tarted POST "/stats" for 92.133.16.18 at 2015-06-09 12:09:11 +0000
Processing by StatsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"bCV+ymN4NxuMM6441OLaRyu/cuLXIcX5fu1g/rG+gqg=", "stat"=>{"id"=>"", "cc"=>"ok", "ct"=>"ok", "force"=>"ok", "endurance"=>"ok", "blessure"=>"ok", "init"=>"ok", "attaque"=>"ok", "ld"=>"ok", "sv"=>"ok"}, "commit"=>"Save"}
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "stats" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2015-06-09 12:09:11.561787"], ["updated_at", "2015-06-09 12:09:11.561787"]]
(10.8ms) commit transaction
Redirected to https://codex-bobix.c9.io/stats
Completed 302 Found in 16ms (ActiveRecord: 11.2ms)
the log for the update action
Started PATCH "/stats/11" for 92.133.16.18 at 2015-06-09 12:04:09 +0000
Processing by StatsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"bCV+ymN4NxuMM6441OLaRyu/cuLXIcX5fu1g/rG+gqg=", "stat"=>{"id"=>"11", "statname"=>"tt", "cc"=>"tt", "ct"=>"t", "force"=>"ttt", "endurance"=>"tt", "blessure"=>"t", "init"=>"ttt", "attaque"=>"tt", "ld"=>"ttt", "sv"=>"tt"}, "commit"=>"save", "id"=>"11"}
Stat Load (0.2ms) SELECT "stats".* FROM "stats" WHERE "stats"."id" = ? LIMIT 1 [["id", 11]]
(0.1ms) begin transaction
SQL (0.5ms) UPDATE "stats" SET "attaque" = ?, "blessure" = ?, "cc" = ?, "ct" = ?, "endurance" = ?, "force" = ?, "init" = ?, "ld" = ?, "statname" = ?, "sv" = ?, "updated_at" = ? WHERE "stats"."id" = 11 [["attaque", "tt"], ["blessure", "t"], ["cc", "tt"], ["ct", "t"], ["endurance", "tt"], ["force", "ttt"], ["init", "ttt"], ["ld", "ttt"], ["statname", "tt"], ["sv", "tt"], ["updated_at", "2015-06-09 12:04:09.051306"]]
(15.9ms) commit transaction
Redirected to https://codex-bobix.c9.io/stats
Completed 302 Found in 25ms (ActiveRecord: 16.8ms)
.
class StatsController < ApplicationController
def index
#stats = Stat.all
end
def show
#stat = Stat.find(params[:id])
#units = #stat.units
end
def new
#stat = Stat.new
end
def edit
#stat = Stat.find(params[:id])
end
def update
#stat = Stat.find(params[:id])
#stat.update(stat_params)
redirect_to "/stats"
flash[:notice] = "work"
end
def new
#stat = Stat.new
end
def create
#stat = Stat.new
if #stat.save
redirect_to "/stats"
flash[:notice] = "work"
else
render "new"
flash[:notice] = "didn't work"
end
end
private
def stat_params
params.require(:stat).permit(:cc, :ct, :force, :endurance, :blessure, :init, :attaque, :ld, :sv, :id, :statname)
end
end
In your create action you are not passing any arguments for your Stat, it should be something like:
class StatsController < ApplicationController
def create
#stat = Stat.new stat_params
# ...
end
private
def stat_params
params.require(:stat).permit(:id, :cc, :ct, :force, :endurance, :blessure, :init, :attaque, :ld, :sv)
end
end
Hope that helps!
When you say #stat = Stat.new, you're instantiating a new Stat object with no assigned attributes, and then saving that. As such, no matter what parameters you pass to the controller, you will only have the fields which are automatically generated (in this case ID). What you need to do is take the parameters and create a new object from them (i.e. #stat = Stat.new( stat_params )).
Since you have a stat_params method defined at the bottom of your controller (as is generally the case by default when you generate a scaffold), then that line of code alone will fix it. Essentially all you need to do is grab the params for the stat (submitted from new), turn them into a hash, and put them into the Stat.new( ... ) statement.

Lock value of database column based on boolean value in Rails

I want to let a user edit the field report.plan only if report.published = false. If report.published = true and they try to save a change, I want to throw an error.
I've written the following code to do this:
class Report < ActiveRecord::Base
validate :cannot_update_plan_after_published, on: :publish_plan!
def publish_plan!(plan)
self.plan = plan
self.published = true
self.save
end
private
def cannot_update_plan_after_published
if self.published?
errors.add(:plan, "You cannot change the plan once it has been published.")
end
end
end
However, this is not working. When I call publish_plan! on an already published report, it makes the save. For example:
> f = Report.last
=> #<Report id: 12, plan: "a", published: false>
> f.publish_plan!("b")
(0.1ms) begin transaction
(0.4ms) UPDATE "reports" SET "plan" = 'b', "updated_at" = '2014-09-18 18:43:47.459983' WHERE "reports"."id" = 12
(9.2ms) commit transaction
=> true
> f = Report.last
Report Load (0.2ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" DESC LIMIT 1
=> #<Report id: 12, plan: "b", published: true>
> f.publish_plan!("c")
(0.1ms) begin transaction
(0.4ms) UPDATE "reports" SET "plan" = 'c', "updated_at" = '2014-09-18 18:43:53.996191' WHERE "reports"."id" = 12
(8.7ms) commit transaction
=> true
> Report.last
Report Load (0.2ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" DESC LIMIT 1
=> #<Report id: 12, plan: "c", published: true>
How do I get this field to become uneditable once report.published = true?
Try removing the on: :public_plan!. That way, the validation should be run every time the model is saved.
validate :cannot_update_plan_after_published
See here for more details: Adding a validation error with a before_save callback or custom validator?
Also, for the validation method itself, change it to the following:
def cannot_update_plan_after_published
if self.published? && self.published_changed? == false
errors.add(:plan, "You cannot change the plan once it has been published.")
end
end
This allows you to set it the first time publishing the plan.

Rails update_Attributes(params[:user])

I have a User show function that makes an api call and updates the user attributes, but I'm having trouble saving the changes.
def show
#user = User.find(params[:id])
... change attributes of #user ...
if #user.update_attributes(params[:user])
p "Saved"
else
p "Failed"
end
end
I'm updated two attributes (:today_steps and :year). Both are accessible in the user.rb. The challenge is that :year is a Serialized Array, but I'm not sure how that changes anything
this setup has returned a 'Failed' every time. i have also tried 'params[:id]' but I receive the error
undefined method `stringify_keys' for "1":String
Any ideas on how to solve this? thanks
more info
def show
#user = User.find(params[:id])
if (#user.device != nil)
#device = #user.device
client = Fitgem::Client.new(
#api client - haven't figured out my security yet, so I won't post it
)
now = Time.now()
last = #device.lastUpdated
while now.to_date > last.to_date
day = client.activities_on_date last.strftime("%Y-%m-%d")
break if day['errors'] != nil
day = day['summary']['steps']
last = last + 1.days
#user.year = #user.year.unshift(day)
end
day = (client.activities_on_date 'today')
if day['errors'] == nil
#user.today_steps = day['summary']['steps']
if now.day != last.day
#user.year.unshift(#user.today_steps)
else
#user.year[0] = #user.today_steps
end
end
#device.lastUpdated = now
p #user.year
#user.device.save
if #user.update_attributes(params[:user])
p "Updated"
else
p "Failed to Update"
end
end
#competitions = #user.competitions.paginate(page: params[:page])
end
_
User.rb
attr_accessible :email, :name, :password, :password_confirmation, :year, :today_steps
serialize :year, Array
Even More
console, using params[:user]
Started GET "/users/2" for 127.0.0.1 at 2014-02-25 17:15:09 -0800
Processing by UsersController#show as HTML
Parameters: {"id"=>"2"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", "2"]]
Device Load (0.1ms) SELECT "devices".* FROM "devices" WHERE "devices"."user_id" = 2 LIMIT 1
(0.1ms) begin transaction
(0.3ms) UPDATE "devices" SET "lastUpdated" = '2014-02-26 01:15:09.634622', "updated_at" = '2014-02-26 01:15:10.392810' WHERE "devices"."id" = 3
(2.3ms) commit transaction
(0.1ms) begin transaction
User Exists (0.1ms) SELECT 1 FROM "users" WHERE (LOWER("users"."email") = LOWER('fire#fox.com') AND "users"."id" != 2) LIMIT 1
(0.0ms) rollback transaction
Rendered users/show.html.erb within layouts/application (0.4ms)
Rendered layouts/_shim.html.erb (0.0ms)
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = '1f47eae1cb002cb1c4e83ce0363fbfb2d33ad8f5' LIMIT 1
Rendered layouts/_header.html.erb (2.2ms)
Rendered layouts/_footer.html.erb (0.1ms)
console #user after find
#<User id: 1, name: "Marcus", email: "mar#comcast.net", points: nil, created_at: "2014-02-25 21:59:12", updated_at: "2014-02-25 21:59:12", password_digest: "$2aGTx..FbLP...", admin: false, remember_token: "e4bdd9c", year: [], lastUpdate: nil, lastUpdated: nil, today_steps: nil>
Your code is as
#user = User.find(params[:id])
... change attributes of #user ...
if #user.update_attributes(params[:user])
#the log
Started GET "/users/2" for 127.0.0.1 at 2014-02-25 17:15:09 -0800
Processing by UsersController#show as HTML
Parameters: {"id"=>"2"}
the log clearly states that the show method receives only 'id' whereas in your controller your expect a hash 'user' which can be supplied to update_attributes.
'show' expects to get an attributes hash to stringify it's keys for the column names.

Resources