I am unable to debug some of cancancan code and looking for some help.
can :update, Message do |m|
false
end
But strangely cancancan always return true and i am unable to debug the issue.Here is a small piece of relevant cancancan code
rule.rb
def matches_conditions?(action, subject, extra_args)
if
...
elsif #block && !subject_class?(subject)
#block.call(subject, *extra_args)
end
end
I can see that the #block.call is made and it return false but this piece of code in the ability.rb does not make sense to me
match = subject.map do |subject|
relevant_rules_for_match(action, subject).detect do |rule|
rule.matches_conditions?(action, subject, extra_args)
end
end.compact.first
match ? match.base_behavior : false
The base_behaviour is never set after initialization, and set to true at initialization, how would base_behaviour of a rule would ever return false.
The helper function is called
ability = Ability.new(user)
ability.can? :update ,#message
Got it there was a overriding ability
Related
I've got this method
def finalize_inquiry_process(form)
if finalize_process == true
inquiry_process.campaign_code.update(state: 'used')
document_creator_class = InquiryProcessDocumentCreatorFetcher.new(inquiry_process).call
document_creator_class.new(inquiry_process).call
end
Success(form)
end
and I want to skip in specs this part which is really trouble maker, implementation is an unnecessary waste of time (pdf generator with tons of fields)
document_creator_class = InquiryProcessDocumentCreatorFetcher.new(inquiry_process).call
document_creator_class.new(inquiry_process).call
To do so I wrote a specs:
let(:fetcher_instance) { instance_double(InquiryProcessDocumentCreatorFetcher) }
before do
allow(InquiryProcessDocumentCreatorFetcher).to receive(:new).and_return(fetcher_instance)
allow(fetcher_instance).to receive(:call).and_return(nil)
end
it 'updates state of assigned campain code' do
updated_inquiry_process = process_update.value!
expect(updated_inquiry_process.campaign_code.state).to eq('used')
end
end
InquiryProcesses::Update.call campain code updates state of assigned campain code
Failure/Error: document_creator_class.new(inquiry_process).call
NoMethodError:
undefined method `new' for nil:NilClass
Is there any chance to skip this part of code in specs?
Ok I managed it by using receive_message_chain helper. Specs should looked like this:
describe 'finalize inquiry process' do
subject(:process_update) do
described_class.new(
inquiry_process: inquiry_process,
form: loan_application_inquiry_process_update_form,
finalize_process: true,
).call
end
let!(:inquiry_process) do
create :inquiry_process, inquiry_template: loan_inquiry_template, campaign_code_uid: campaign_code.uid
end
before do
allow(InquiryProcessDocumentCreatorFetcher).to receive_message_chain(:new, :call, :new, :call)
end
it 'updates state of assigned campain code' do
updated_inquiry_process = process_update.value!
expect(updated_inquiry_process.campaign_code.state).to eq('used')
end
end
You can try your luck with dependency injection:
(It's a rough sketch, but I don't know the context of the system, not even the whole class)
def initialize(inquiry_process:, form:, finalize_process:, creator_fetcher:)
#creator_fetcher = creator_fetcher
# all the other initializetions
# maybe you can initialize creator_fetcher outside, and no need to pass inquiry_process anymore?
end
def creator_fetcher
#creator_fetcher ||= InquiryProcessDocumentCreatorFetcher.new(inquiry_process)
end
def finalize_inquiry_process(form)
if finalize_process == true
inquiry_process.campaign_code.update(state: 'used')
document_creator_class = creator_fetcher.call
document_creator_class.new(inquiry_process).call
end
Success(form)
end
and then
let(:creator_fetcher) { instance_double(InquiryProcessDocumentCreatorFetcher) }
let(:document_creator_class) { instance_double(whatever_fetcher_call_returns_or_just_unveryfying_double) }
before { allow(creator_fetcher.to_receive(:call).and_return(document_creator_class) }
subject(:process_update) do
described_class.new(
inquiry_process: inquiry_process,
form: loan_application_inquiry_process_update_form,
finalize_process: true,
).call
end
Anyway - your problems with tests show's that your code was not written with the tests, and it's a bad design.
Indirection (Dependency Injection here) might help a little to untangle the mess.
Question:
I need to know the records' attributes that have been called inside a block (say I need something like the following):
def my_custom_method(&block)
some_method_that_starts_tracking
block.call
some_method_that_stops_tracking
puts some_method_that_returns_called_records_attributes
do_something_about(some_method_that_returns_called_records_attributes)
end
my_custom_method { somecodethatcallsauthorofbook1andemailandfirstnameofuser43 }
# this is the `puts` output above (just as an example)
# => {
# #<Book id:1...> => [:author],
# #<User id:43...> => [:email, :first_name]
# }
code inside the block can be anything
Specifically, I meant to track any instance of a subclass of ApplicationRecord, so it can be instance of any models like Book, User, etc...
Attempts:
From my understanding, this is similar to how rspec works when a method is expected to be called. That it somehow tracks any calls of that method. So, my initial attempt is to do something like the following (which does not yet fully work):
def my_custom_method(&block)
called_records_attributes = {}
ApplicationRecord.descendants.each do |klass|
klass.class_eval do
attribute_names.each do |attribute_name|
define_method(attribute_name) do
called_records_attributes[self] ||= []
called_records_attributes[self] << attribute_name
self[attribute_name]
end
end
end
end
block.call
# the above code will work but at this point, I don't know how to clean the methods that were defined above, as the above define_methods should only be temporary
puts called_records_attributes
end
my_custom_method { Book.find_by(id: 1).title }
# => {
# #<Book id: 1...> => ['title']
# }
the .descendants above probably is not a good idea because Rails use autoload if I'm not mistaken
as already said above in the comment, I do not know how to remove these "defined_methods" that are just supposed to be only temporary for the duration of this "block".
furthermore, my code above would probably have overriden the "actual" attribute getters of the models, if ever any has been already defined, which is bad.
Background:
I am writing a gem live_record which I am adding a new feature that will allow a developer to just simply write something like
<!-- app/views/application.html.erb -->
<body>
<%= live_record_sync { #book.some_custom_method_about_book } %>
</body>
... which will render #book.some_custom_method_about_book as-is on the page, but at the same time the live_record_sync wrapper method would take note of all the attributes that have been called inside that block (i.e. inside some_custom_method_about_book the #book.title is called), and then it sets these attributes as the block's own "dependencies", in which later when that specific book's attribute has been updated, I can already also update directly the HTML page of which this attribute is a "dependency" as like specified just above. I am aware that this is not an accurate solution, but I'd like to open up my chances by experimenting on this first.
-- Rails 5
Disclaimer: I believe this is just a mediocre solution, but hopefully helps anyone with the same problem.
I tried reading rspec source code, but because I couldn't easily comprehend what is happening under the hood, and that it occurred to me that rspec's (i.e.) expect(Book.first).to receive(:title) is different from what I really want because the methods there are already specified (i.e. :title), while what I want is to track ANY methods that are attributes, so because of these two reasons I skipped reading further, and attempted my own solution, which hopefully did somehow work; see below.
Note that I am using Thread local-storage here, so this code should be thread-safe (untested yet).
# lib/my_tracker.rb
class MyTracker
Thread.current[:my_tracker_current_tracked_records] = {}
attr_accessor :tracked_records
class << self
def add_to_tracked_records(record, attribute_name)
Thread.current[:my_tracker_current_tracked_records][{model: record.class.name.to_sym, record_id: record.id}] ||= []
Thread.current[:my_tracker_current_tracked_records][{model: record.class.name.to_sym, record_id: record.id}] << attribute_name
end
end
def initialize(block)
#block = block
end
def call_block_while_tracking_records
start_tracking
#block_evaluated_value = #block.call
#tracked_records = Thread.current[:my_tracker_current_tracked_records]
stop_tracking
end
def to_s
#block_evaluated_value
end
# because I am tracking record-attributes, and you might want to track a different object / method, then you'll need to write your own `prepend` extension (look for how to use `prepend` in ruby)
module ActiveRecordExtensions
def _read_attribute(attribute_name)
if Thread.current[:my_tracker_current_tracked_records] && !Thread.current[:my_tracker_is_tracking_locked] && self.class < ApplicationRecord
# I added this "lock" to prevent infinite loop inside `add_to_tracked_records` as I am calling the record.id there, which is then calling this _read_attribute, and then loops.
Thread.current[:my_tracker_is_tracking_locked] = true
::MyTracker.add_to_tracked_records(self, attribute_name)
Thread.current[:my_tracker_is_tracking_locked] = false
end
super(attribute_name)
end
end
module Helpers
def track_records(&block)
my_tracker = MyTracker.new(block)
my_tracker.call_block_while_tracking_records
my_tracker
end
end
private
def start_tracking
Thread.current[:my_tracker_current_tracked_records] = {}
end
def stop_tracking
Thread.current[:my_tracker_current_tracked_records] = nil
end
end
ActiveSupport.on_load(:active_record) do
prepend MyTracker::ActiveRecordExtensions
end
ActiveSupport.on_load(:action_view) do
include MyTracker::Helpers
end
ActiveSupport.on_load(:action_controller) do
include MyTracker::Helpers
end
Usage Example
some_controller.rb
book = Book.find_by(id: 1)
user = User.find_by(id: 43)
my_tracker = track_records do
book.title
if user.created_at == book.created_at
puts 'same date'
end
'thisisthelastlineofthisblockandthereforewillbereturned'
end
puts my_tracker.class
# => #<MyTracker ... >
puts my_tracker.tracked_records
# => {
# {model: :Book, record_id: 1} => ['title', 'created_at'],
# {model: :User, record_id: 43} => ['created_at']
# }
puts my_tracker
# => 'thisisthelastlineofthisblockandthereforewillbereturned'
# notice that `puts my_tracker` above prints out the block itself
# this is because I defined `.to_s` above.
# I need this `.to_s` so I can immediately print the block as-is in the views.
# see example below
some_view.html.erb
<%= track_records { current_user.email } %>
P.S. Maybe it's better that I wrap this up as a gem. If you're interested, let me know
I tried rewriting this function numerous ways to get around this error, however, I want to defer to other experts before I disable the cop around it.
def numeric?(obj)
obj.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
end
This is used like so:
def index
if params[:job_id] && numeric?(params[:job_id])
This issue was solved via: Checking if a variable is an integer
Update trying:
def numeric?(string)
!!Kernel.Float(string)
rescue TypeError, ArgumentError
false
end
Reference How do I determine if a string is numeric?
New error:
def numeric?(arg)
!/\A[+-]?\d+\z/.match(arg.to_s).nil?
end
Passes all Rubocop tests from a default configuration. Complete gist with tests at https://gist.github.com/aarontc/d549ee4a82d21d263c9b
The following code snippet does the trick:
def numeric?(arg)
return false if arg.is_a?(Float)
return !Integer(arg).nil? rescue false
end
Returns false for the following: 'a', 12.34, and '12.34'.
Returns true for the following: '1', 1.
You can write the method
def numeric?(obj)
obj.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/).nil?
end
You really don't need to do nil comparisons and then based on the decision returning true/false. #nil? method does it for you.
My syntax error is solved, but now I've got another problem.
I have a model User and Post. Also i've created the additional model which is called Like(for likes/dislikes system). So, i wrote a method to check if the copy of the model Like has the given 'post_id' and 'user_id' simulteniously, then the given Post was liked by the given User. Here's the code of my method
def licked(p,u)#p - post, u - user
if Like.all.empty? then return false
else
if (Like.where(post_id: p.id).empty?)
return false
else
posts=Like.where(post_id: p.id)
if ((posts.length<2) && (posts[0].user_id==u.id) && (posts[0].action!=nil))==true then return true
else
if ((posts.length<2) && (posts[0].user_id!=u.id)&& (posts[0].action!=nil)) then return false
else posts.each do |i|
if (i.user_id==u.id&& (posts[0].action!=nil)) then return true
end
end
end
end
end
end
end
Sorry, if there are many if's, i did not find any other way to relize it, and anyway it's not working. So i'm asking for your help: either to hange the existing code or making it other way, thanks.
You don't need ANY of that logic.
def liked(post, user)
Like.where(user_id: user.id, post_id: post.id).where.not(action: nil).exists?
end
To check if buyer.save is going to fail I use buyer.valid?:
def create
#buyer = Buyer.new(params[:buyer])
if #buyer.valid?
my_update_database_method
#buyer.save
else
...
end
end
How could I check if update_attributes is going to fail ?
def update
#buyer = Buyer.find(params[:id])
if <what should be here?>
my_update_database_method
#buyer.update_attributes(params[:buyer])
else
...
end
end
it returns false if it was not done, same with save. save! will throw exceptions if you like that better. I'm not sure if there is update_attributes!, but it would be logical.
just do
if #foo.update_attributes(params)
# life is good
else
# something is wrong
end
http://apidock.com/rails/ActiveRecord/Base/update_attributes
Edit
Then you want this method you have to write. If you want to pre check params sanitation.
def params_are_sanitary?
# return true if and only if all our checks are met
# else return false
end
Edit 2
Alternatively, depending on your constraints
if Foo.new(params).valid? # Only works on Creates, not Updates
#foo.update_attributes(params)
else
# it won't be valid.
end
The method update_attributes returns false if object is invalid. So just use this construction
def update
if #buyer.update_attributes(param[:buyer])
my_update_database_method
else
...
end
end
If your my_update_database_method has to be call only before update_attributes, then you shoud use merge way, probably like this:
def update
#buyer = Buyer.find(params[:id])
#buyer.merge(params[:buyer])
if #buyer.valid?
my_update_database_method
#buyer.save
else
...
end
end
This may not be the best answer, but it seems to answer your question.
def self.validate_before_update(buyer)#parameters AKA Buyer.validate_before_update(params[:buyer])
# creates temporary buyer which will be filled with parameters
# the temporary buyer is then check to see if valid, if valid returns fail.
temp_buyer = Buyer.new
# populate temporary buyer object with data from parameters
temp_buyer.name = buyer["name"]
# fill other required parameters with valid data
temp_buyer.description = "filler desc"
temp_buyer.id = 999999
# if the temp_buyer is not valid with the provided parameters, validation fails
if temp_buyer.valid? == false
temp_buyer.errors.full_messages.each do |msg|
logger.info msg
end
# Return false or temp_buyer.errors depending on your need.
return false
end
return true
end
you'd better check it in your model through a before_save
before_save :ensure_is_valid
private
def ensure_is_valid
if self.valid?
else
end
end
I've run into the same scenario - needed to know if record is valid and do some actions before update save. I've found out that there is assign_attributes(attributes) method which update method uses before save. So nowadays it's likely correct to do:
def update
#buyer = Buyer.find(params[:id])
#buyer.assign_attributes(params[:buyer])
if #buyer.valid?
my_update_database_method
#buyer.save
else
...
end
end