Rails CanCanCan & Defining Abilities - ruby-on-rails

I am trying to make an app in Rails 4.
I am using CanCanCan for permissions and Role_Model for roles management.
In my ability.rb, I have defined student abilities as:
elsif user.try(:profile).present? && user.profile.has_role?(:student)
student_abilities
and then:
def student_abilities
can :read, Project.visible.current.available
In my project.rb I have defined scopes as:
scope :visible, lambda { joins(:sweep => :disclosure).where('disclosures.allusers' => 'true')
.joins(:sweep => :finalise).where('finalises.draft' => 'false') }
scope :current, lambda { where('project.start_date >= ?', Date.today)}
scope :available, lambda { where('closed =', 'false')}
When I try to start the server and generate a view, I get this error:
NoMethodError at /project_invitations
undefined method `available' for #<Project::ActiveRecord_Relation:0x007fdde41f2ee8>
When I try removing available from the end of the ability, so that its just:
can :read, Project.visible.current
I get this error:
entry for table "project"
LINE 1: ..." = 'true' AND "finalises"."draft" = 'false' AND (project.st...
^
I don't know why it won't let me read the end of the error message.
Can anyone see what I've done wrong?

Check the table name. Is it really called "project", not "projects"?
The way you describe scopes is a bit weird. E.g. instead of where('closed
=', 'false') I would describe it as where(closed: false), minimizing the number of SQL-aware fragments

Related

ArgumentError: The scope body needs to be callable

I have the following methods defined in a plugin:
class ReArtifactProperties < ActiveRecord::Base
unloadable
#attr_accessible :artifact_type
scope :without_projects, :conditions => ["artifact_type != ?", 'Project']
scope :of_project, lambda { |project|
project_id = (project.is_a? Project) ? project.id : project
{:conditions => {:project_id => project_id}}
}
Your code has several issues, some of which are violations of syntax rules of Ruby, while others violate return value requirements of Rails.
The cause of your current error is a syntax error. The block to the lambda must be defined in the same line as the lambda method.
Now, if you have fixed this, you will notice that your code will throw other exceptions once you use the scope. The reason for that is that the return value of a scope is expected to be an ActiveRecord relation, not just a simple Hash.
Your scope definition should thus look similar to this:
scope :of_project, lambda { |project|
project_id = (project.is_a? Project) ? project.id : project
where(:project_id => project_id)
}
Now given that Rails is smart enough to figure out how to get the ID from an object for a query, you can even get rid of the project_id logic in it and reduce your scope definition to
scope :of_project, lambda { |project|
where(:project_id => project)
}
Your code has several issues, some of which are violations of syntax rules of Ruby, while others violate return value requirements of Rails.
Yes you were right about the lambda block
Initially the code was:
scope :of_project, lambda { |project|
project_id = (project.is_a? Project) ? project.id : project
{:conditions => {:project_id => project_id}}
}
After Correction, it reduced down to this:
scope :of_project, lambda { |project|
project_id = (project.is_a? Project) ? project.id : project
where(:project_id => project_id)
}
Now given that Rails is smart enough to figure out how to get the ID from an object for a query,
Knowing the MVC architecture which I learned from http://rubyonrails.org/
How do I understand the core functions of RAILS w.r.t Redmine Tool?The mysql default database that it uses has a very huge model structure currently.Could it be reduced down to understand the core functions of Rails?

is it possible to have conditional default scope in rails?

I am on rails 3.2.21, ruby version is 2.0
My requirement is to have role based conditional default scope for a particular model. eg
consider role variable as an attribute of logged in user
if role == 'xyz'
default_scope where(is_active: false)
elsif role == 'abc'
default_scope where(is_active: true)
end
Nothing is impossible in programming.
Using default_scope is a bad idea in general (lots of articles are written on the topic).
If you insist on using current user's atribute you can pass it as an argument to scope:
scope :based_on_role, lambda { |role|
if role == 'xyz'
where(is_active: false)
elsif role == 'abc'
where(is_active: true)
end
}
And then use it as follows:
Model.based_on_role(current_user.role)
Sidenote: Rails 3.2.x - seriously?...
default_scope where(
case role
when 'xyz' then { is_active: false }
when 'abc' then { is_active: true }
else '1 = 1'
end
)
Also, please read the answer by Andrey Deineko, specifically the part about default scopes usage.

Ransack + FlagShihTzu + Active Admin don't play well together

I'm using the brilliant gem flag_shih_tzu to create bitwise boolean flags on a single integer column without requiring a separate DB column for each flag. I have loved this gem for many years now, and it's quite excellent at interplaying with ActiveRecord attributes in all the ways you would normally expect.
However, it does not play well with Ransack and Active Admin out of the box. Active Admin requires me to add permitted params for each flag:
permit_params do
:identity_verified
end
for the :identity_verified "flag" attribute to even show up in filters or index columns, which is fine; I don't mind that. But the real problem I'm having is when I try to use the :identity_verified flag as a filter (it's boolean, of course), Active Admin shows the normal select option for it with Any/Yes/No, but when I first submitted the filter query, I got an exception: undefined method identity_verified_eq for Ransack::Search.
Ok, so I did some research and figured out that I need to add a ransacker for :identity_verified. Did that, and I don't get the exception anymore, but the ransacker doesn't appear to do anything at all. In fact, I intentionally put a syntax error in the block to cause an exception, but Active Admin just returns all the Users, regardless of whether they're :identity_verified or not. That code inside the ransacker block doesn't seem to even get executed. Can anyone help me figure out how to create a proper Ransack definition for :identity_verified?
Here's the code:
User Model :
# ===============
# = FlagShihTzu =
# ===============
has_flags 1 => :guest,
2 => :prospect,
3 => :phone_verified,
4 => :identity_verified,
# ==============
# = Ransackers =
# ==============
# this avoids the identity_verified_eq missing method exception, but
# doesn't appear to do anything else
ransacker :identity_verified, args: [:ransacker_args] do |args|
asdf # <-- should cause an exception
Arel.sql(identity_verified_condition)
end
Active Admin:
ActiveAdmin.register User do
permit_params do
:identity_verified
end
# ...
filter :identity_verified, as: :boolean
# ...
end
The Identity Verified filter shows up as a boolean select in Active Admin, like I expect, but when I submit the filter, (as I metioned above), I get all the Users back, and the ransacker block doesn't even seem to get executed. I've read the Ransack examples. I've dug into the code of all four gems (including Formtastic), and I still can't sort it out.
Here's the POST URL from Active Admin on query submit:
http://example.com/admin/users?q%5Bidentity_verified%5D=true&commit=Filter&order=id_desc
Here's the Rails log to confirm the :identity_verified param is getting passed:
Processing by Admin::UsersController#index as HTML
Parameters: {"utf8"=>"✓", "q"=>{"identity_verified"=>"true"}, "commit"=>"Filter", "order"=>"id_desc"}
Again, the inside of the ransacker block doesn't seem to get executed. I need to understand why, and if it ever does, how to write the proper Arel statement so I can filter on this flag.
Help?
Resurrecting this question as it is the first result on G* here searching for "flag shih tzu activeadmin". Plus it seems OP's solution is not ideal in that it loads & instantiates AR objects for all records fulfilling the flag condition in this part:
results = object_class.send("#{flag}")
results = results.map(&:id)
So here's my current solution for others:
# config/initializers/ransack.rb
Ransack.configure do |config|
config.add_predicate 'flag_equals',
arel_predicate: 'eq',
formatter: proc { |v| (v.downcase == 'true') ? 1 : 0 },
validator: proc { |v| v.present? },
type: :string
end
# app/concerns/has_flags.rb
module HasFlags
extend ActiveSupport::Concern
included { include FlagShihTzu }
class_methods do
def flag_ransacker(flag_name, flag_col: 'flags')
ransacker(flag_name) do |parent|
Arel::Nodes::InfixOperation.new('DIV',
Arel::Nodes::InfixOperation.new('&', parent.table[flag_col], flag_mapping[flag_col][flag_name]),
flag_mapping[flag_col][flag_name])
end
end
end
end
# app/models/foo.rb
class Foo < ActiveRecord::Base
include HasFlags
has_flags 1 => :bar, 2 => :baz
flag_ransacker :bar
end
# app/admin/foos.rb
ActiveAdmin.register Foo do
filter :bar_flag_equals, as: :boolean, label: 'Bar'
end
So, I finally figured out the answer after luckily stumbling on to this post ActiveAdmin Filters with Ransack. The gist of it is properly defining the Active Admin filter using the DSL and more importantly, the appropriate ransacker in the model for the FlagShihTzu flag you want to filter on.
Here's a working example:
models/user.rb:
class User
include FlagShihTzu
# define the flag
has_flags 1 => :identity_verified
# convenience method to define the necessary ransacker for a flag
def self.flag_ransacker(flag)
ransacker flag.to_sym,
formatter: proc { |true_false|
if true_false == "true"
results = object_class.send("#{flag}")
else
results = object_class.send("not_#{flag}")
end
results = results.map(&:id)
results = results.present? ? results : nil
}, splat_params: true do |parent|
parent.table[:id]
end
end
admin/user.rb:
ActiveAdmin.register User do
# A method used like a standard ActiveAdmin::Resource `filter` DSL call, but for FlagShizTzu flags
# A corresponding `flag_ransacker` call must be made on the model, which must include
# the FlagShizTzuRansack module defined in app/concerns/models/flag_shih_tzu_ransack.rb
def flag_filter(flag)
#resource.flag_ransacker flag.to_sym # call the ransacker builder on the model
flag = flag.to_s
filter_name = "#{flag}_in" # we use the 'in' predicate to allow multiple results
filter filter_name.to_sym,
:as => :select,
:label => flag.gsub(/[\s_]+/, ' ').titleize,
:collection => %w[true false]
end
flag_filter :identity_verified
end
And voila, a working sidebar filter for flag-shih-tzu flags. The key was adding the in predicate at the end of the flag name in the filter declarion, instead of excluding it, which defaults to the eq Ransack predicate. Defining the ransacker itself took trial and error using pry and the debugger, but was based largely on the aforementioned post.
Ultimately, I ended up pulling out the inline methods in the two files into modules that I include in the necessary models and AA resource definitions that need them.
app/concerns/models/flag_shih_tzu_ransack.rb:
# Used to define Ransackers for ActiveAdmin FlagShizTzu filters
# See app/admin/support/flag_shih_tzu.rb
module FlagShihTzuRansack
extend ActiveSupport::Concern
module ClassMethods
# +flags are one or more FlagShihTzu flags that need to have ransackers defined for
# ActiveAdmin filtering
def flag_ransacker(*flags)
object_class = self
flags.each do |flag|
flag = flag.to_s
ransacker flag.to_sym,
formatter: proc { |true_false|
if true_false == "true"
results = object_class.send("#{flag}")
else
results = object_class.send("not_#{flag}")
end
results = results.map(&:id)
results = results.present? ? results : nil
}, splat_params: true do |parent|
parent.table[:id]
end
end
end
end
end
app/admin/support/flag_shih_tzu.rb:
# Convenience extension to filter on FlagShizTzu flags in the AA side_bar
module Kandidly
module ActiveAdmin
module DSL
# used like a standard ActiveAdmin::Resource `filter` DSL call, but for FlagShizTzu flags
# A corresponding `flag_ransacker` call must be made on the model, which must include
# the FlagShizTzuRansack module defined in app/concerns/models/flag_shih_tzu_ransack.rb
def flag_filter(flag)
#resource.flag_ransacker flag.to_sym # call the ransacker builder on the model
flag = flag.to_s
filter_name = "#{flag}_in" # we use the 'in' predicate to allow multiple results
filter filter_name.to_sym,
:as => :select,
:label => flag.gsub(/[\s_]+/, ' ').titleize,
:collection => %w[true false]
end
end
end
end
ActiveAdmin::ResourceDSL.send :include, Kandidly::ActiveAdmin::DSL
Then more cleanly, in the model:
class User
include FlagShihTzu
include FlagShihTzuRansack
# define the flag
has_flags 1 => :identity_verified
end
and in the resource definition:
ActiveAdmin.register User do
flag_filter :identity_verified
end
There are probably more elegant implementations of the methods, but having a working solution, I'm moving on. Hope this helps whoever up-voted this question. Ransack documentation leaves a bit to be desired. Thanks to Russ for his post on Jaguar Design Studio, and to the commenters on https://github.com/activerecord-hackery/ransack/issues/36, who helped me better understand how Ransack works. In the end I had to dig into the gems to get my final solution, but I would not have known where to start without their contributions.

Argument Error: The scope body needs to be callable

I'm working through the 'Ruby On Rails 3 Essential Training' and have received a problem when using name scopes. When finding records and using queries withing the Rails console everything went smoothly until I tried to use a name scope in my subject.rb file. This is my code in the subject.rb file.
Class Subject < ActiveRecord::Base
scope :visible, where(:visible => true)
end
I saved the .rb file and restarted my Rails console but when I run from my rails console:
subjects = Subject.visible
I get: ArgumentError: The scope body needs to be callable.
Does anyone know why I'm getting this error.
The scope's body needs to be wrapped in something callable like a Proc or Lambda:
scope :visible, -> {
where(:visible => true)
}
The reason for this is that it ensures the contents of the block is evaluated each time the scope is used.
I got the same error , while before my solution I had a space between where and ( like below
scope :registered , -> { where ( place_id: :place_id , is_registered: :true ) }
after i removed the space between where and ( like below i made my page working
scope :registered , -> { where( place_id: :place_id , is_registered: :true ) }
Yes, indeed, this is rails 4 way of calling scopes. You'd need to change it, if you're upgrading to Rails 4 from Rails 3.
What you are using: scope :visible, where(:visible => true) goes for eager loading, and has been deprecated in Rails 4.
scope :visible, where(:visible => true)
This line of code gets evaluated when the particular class is loaded, and not at the time, this very scope is called.
There are a few cases when this thing does matter, like:
scope :future, where('published_at > ?', Time.now)
scope :future, -> { where('published_at > ?', Time.now) }
In first case, ? will be replaced with the very time the class would have been loaded, but the second & correct case, that time will be used at which the scope would have been called on the class.

Rails - Using a before_filter to run a method

I would like this before filter to run every time the page is loaded (for now) to check if an item is over 7 days old or not and if so, run some actions on it to update its attributes.
I have before_filter :update_it in the application controller. update_it is defined below that in the same controller as:
def update_it
#books = Book.all
#books.each do |book|
book.update_queue
end
end
Then update_queue is defined in the book model. Here's everything in the model that pertains to this:
scope :my_books, lambda {|user_id|
{:conditions => {:user_id => user_id}}
}
scope :reading_books, lambda {
{:conditions => {:reading => 1}}
}
scope :latest_first, lambda {
{:order => "created_at DESC"}
}
def move_from_queue_to_reading
self.update_attributes(:queued => false, :reading => 1);
end
def move_from_reading_to_list
self.update_attributes(:reading => 0);
end
def update_queue
days_gone = (Date.today - Date.parse(Book.where(:reading => 1).last.created_at.to_s)).to_i
# If been 7 days since last 'currently reading' book created
if days_gone >= 7
# If there's a queued book, move it to 'currently reading'
if Book.my_books(user_id).where(:queued => true)
new_book = Book.my_books(user_id).latest_first.where(:queued => true).last
new_book.move_from_queue_to_reading
currently_reading = Book.my_books(user_id).reading_books.last
currently_reading.move_from_reading_to_list
# Otherwise, create a new one
else
Book.my_books(user_id).create(:title => "Sample book", :reading => 1)
end
end
end
My relationship is that a book belongs_to a user and a user has_many books. I'm showing these books in the view through the user show view, not that it matters though.
So the errors I keep getting are that move_from_queue_to_reading and move_from_reading_to_list are undefined methods. How can this be? I'm clearly defining them and then calling them below. I really am at a loss and would greatly appreciate some insight into what I'm doing wrong. I'm a beginner here, so any structured criticism would be great :)
EDIT
The exact error message I get and stack trace is as follows:
NoMethodError in UsersController#show
undefined method `move_from_queue_to_reading' for nil:NilClass
app/models/book.rb:41:in `update_queue'
app/controllers/application_controller.rb:22:in `block in update_it'
app/controllers/application_controller.rb:21:in `each'
app/controllers/application_controller.rb:21:in `update_it'
I suspect that the collection returned is an empty array (which is still 'truthy' when tested). So calling .last is returning nil to the new_book and currently_reading local variables. Try changing:
if Book.my_books(user_id).where(:queued => true)
to:
if Book.my_books(user_id).where(:queued => true).exists?
Additionally, you are modifying the scope when finding currently_reading. This can potentially cause the query to again return no results. Change:
currently_reading.move_from_reading_to_list
to:
currently_reading.move_from_reading_to_list if currently_reading

Resources