here is my ability define:
can :manage,Order,:store => {:business_district => {:district_manager_id => user.id}}
and when I loading resourses by:
Order.accessible_by(current_ability)
It generate SQL like this:
SELECT COUNT(*) FROM `orders` INNER JOIN `stores` ON `stores`.`id` = `orders`.`store_id` INNER JOIN `business_districts` ON `business_districts`.`id` = `stores`.`business_district_id` WHERE (`stores`.`business_districts` = '---\n:district_manager_id: 37\n')
The sql looks all good except code in "where".
I don't know how to make it works
here is the model relation
1. store belongs_to business_district
2. order belongs_to store
3. business_district has district_manager_id
and i want see all orders in business_district
I have found a topic about this in so:[CanCan deeply nested resources
but when use block define in cancan ,you can't use accessible_by(current_ability) load resources!
and your can use Order.joins(:store => :business_district).where("district_manager_id = ?",current_user.id)to load what I want! is there any way cancan gem can do like this?
Update (28 May): The pull request adding deeply nested resource abilities was included in the 1.6.10 release. There is also a fix on GitHub for the SQL conditions bug described below. It should make it into the next release I believe, as it is an obvious mismatch.
Maybe you've figured this out by now, but for anybody else who might stop by here before searching the issues page for CanCan on github....
There's a pull request that was accepted about 2 months ago that addresses this. Check https://github.com/ryanb/cancan/pull/806 .
As of CanCan 1.6.9 this changeset wasn't incorporated so for now one has to use the master branch. As it stands though, the changeset introduces another bug from what I can tell, it fails for 2-condition ability definition, even in the non-nested case. For example, I've found that the following (I am using your models for naming...) fails:
can :manage, Order,:store => {:district_manager_id => user.id, :condition2 => true}
whereas the following is ok:
can :manage, Order,:store => {:district_manager_id => user.id }
I've also posted on the GitHub issue page about this.
Related
I have a search form that queries one table in the database but there are many parameters (language, level, creator etc). The code below works provided the fields in question are filled in but I want to change it to:
a) add more parameters (there are several);
b) allow for a field to be empty
Here's the code in the controller:
#materials = Material.find(:all, :conditions => {:targ_lang => params["targ_lang"],
:inst_lang => params["inst_lang"],
:level => params["level"]})
Totally new to this I'm afraid but a lot of the documentation suggests I should be using "where".
Since Rails 3 you can use the where() function:
#materials = Material.where(targ_lang: params["targ_lang"], inst_lang: params["inst_lang"], level: params["level"])
Also, you could take a look at scopes
These allow you to set what you want to do in the model and call it in the controller for example:
class Material < ActiveRecord::Base
scope :active, where(active_state: true)
end
Then in the controller you do something like:
#active_materials = Material.active
This can be useful if you are joining several models and want to keep your controllers less messy.
To conclude, like #RVG said, seachlogic is quite useful as well as, there are others like Sphinx and Elastic Search. You should take a quick look at these and use the one you feel most confortable with.
If you are using search functionality in your app I suggest using SearchLogic gem
It is easy to use and effective..
SearchLogic
RailsCasts for searchlogic
I have the following associations set up
class bookinghdr
belongs_to :agent
end
class bookingitem
belongs_to :bookinghdr, :include => agent
end
So I was expecting to be able to do the following:
named_scope :prepay, :include=>["bookinghdr"], :conditions => ["bookinghdr.agent.agenttype = 'PP'"]
and in my controller do:
b = Bookingitem.prepay
But that gives me a ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'bookinghdr.agent.agenttype'
However if I don't include the conditions clause then I get a recordset on which I can do:
b = Bookingitem.prepay
b[0].bookinghdr.agent.agenttype
without any error!
I don't want to have to get all the records and then iterate over them to find the ones whose agent has a 'PP# flag. I was hoping that AR would do that for me.
Anybody got any ideas on how to achieve this?
Your question shows that you have not yet fully understood how associations and named scopes work. Since I cannot tell from your question what parts aren't clear, I suggest you read the Association Basics guide at http://guides.rubyonrails.org/v2.3.11/association_basics.html. This should bring you up to speed regarding the concepts you want to implement. After you have read the guide it should all make sense.
I've got a model (a Feature) that can have many Assets. These Assets each have an issue_date. I'm struggling with what seems like a simple ActiveRecord query to find all Features and their Assets with an issue_date of tomorrow, regardless of if there are Assets or not — preferably with one query.
Here's my query right now.
Feature.includes(:assets).where(:assets => { :issue_date => Date.tomorrow })
Unfortunately, this returns only the Features that have Assets with an issue_date of tomorrow. Even stranger, the generated SQL looks like this (tomorrow's obviously the 19th).
SELECT `features`.* FROM `features` WHERE `assets`.`issue_date` = '2011-08-19'
Shouldn't this have an LEFT JOIN in there somewhere? That's the sort of thing I'm going for. Using joins instead of includes does an INNER JOIN, but that's not what I want. Strangely enough, it seems like I'm getting an INNER JOIN-type of behavior. When I run that includes query above, the actual SQL that's spit out looks something like this...
SELECT `features`.`id` AS t0_r0, `features`.`property_id` AS t0_r1,
// every other column from features truncated for sanity
`assets`.`feature_id` AS t1_r1, `assets`.`asset_type` AS t1_r2,
// all other asset columns truncated for sanity
FROM `features`
LEFT OUTER JOIN `assets` ON `assets`.`feature_id` = `features`.`id`
WHERE `assets`.`issue_date` = '2011-08-19'
Which looks like it should work right but it doesn't. I get only the Features that have Assets with an issue_date of tomorrow. Any idea what I'm doing wrong?
I've tried the older, Rails v2 way of doing it…
Feature.find(:all,
:include => :assets,
:conditions => ['assets.issue_date = ?', Date.tomorrow])
Which gives me the same results. There's one Feature I know that doesn't have any Assets for tomorrow, and it's not in that list.
I've also poked around and found similar questions, but I couldn't seem to find one that explained this opposite behavior I'm seeing.
Edit: I'm so close. This gets me all the Feature objects.
Feature.joins("LEFT OUTER JOIN assets on assets.feature_id = feature.id AND asset.issue_date = #{Date.tomorrow}")
It does not, however, get me the matching Assets bundled into the object. With feature as a returned item in the query, feature.assets makes another call to the database, which I don't want. I want feature.assets to return only those I've specified in that LEFT OUTER JOIN call. What else do I need to do to my query?
I thought this would get me what I needed, but it doesn't. Calling feature.assets (with feature as an item returned in my query) does another query to look for all assets related to that feature.
Feature.joins("LEFT OUTER JOIN assets on assets.feature_id = feature.id AND asset.issue_date = #{Date.tomorrow}")
So here's what does work. Seems a little cleaner, too. My Feature model already has a has_many :assets set on it. I've set up another association with has_many :tomorrows_assets that points to Assets, but with a condition on it. Then, when I ask for Feature.all or Feature.name_of_scope, I can specify .includes(:tomorrows_assets). Winner winner, chicken dinner.
has_many :tomorrows_assets,
:class_name => "Asset",
:readonly => true,
:conditions => "issue_date = '#{Date.tomorrow.to_s}'"
I can successfully query Features and get just what I need included with it, only if it matches the specified criteria (and I've set :readonly because I know I'll never want to edit Assets like this). Here's an IRB session that shows the magic.
features = Feature.includes(:tomorrows_assets)
feature1 = features.find_all{ |f| f.name == 'This Feature Has Assets' }.first
feature1.tomorrows_assets
=> [#<Asset id:1>, #<Asset id:2>]
feature2 = features.find_all{ |f| f.name == 'This One Does Not' }.first
feature2.tomorrows_assets
=> []
And all in only two SQL queries.
I had a very similar problem and managed to solve it using the following query;
Feature.includes(:assets).where('asset.id IS NULL OR asset.issue_date = ?', Date.tomorrow)
This will load all features, regardless of whether it has any assets. Calling feature.asset will return an array of assets if available without running another query
Hope that helps someone!
You have to specify the SQL for outer joins yourself, the joins method only uses inner joins.
Feature.joins("LEFT OUTER JOIN assets ON assets.feature_id = features.id").
where(:assets => {:issue_date => Date.tomorrow})
Have you tried:
Feature.joins( :assets ).where( :issue_date => Date.tomorrow );
The guide here suggests the includes method is used to reduce the number of queries on a secondary table, rather than to join the two tables in the way you're attempting.
http://guides.rubyonrails.org/active_record_querying.html
Apologies for the long title, but this is bothering me. I'm new to Rails, so this is my first project. Rails 3.0.3.
In my model, a User may or may not have read many Entries; this is tracked in a model called ReadEntries. This many-to-one relationship is properly defined in the code, I think.
User.rb:
has_many :read_entries
Entry.rb:
has_many :read_entries
ReadEntry.rb:
belongs_to :entry
belongs_to :user
This table has to be populated at some point. If I try to do this:
user.read_entries.find_or_create_by_entry_id(entry.id, :read => false)
I get the error Unknown key(s): read. Leave out trying to set :read, and it works.
However, if I create the same row with this, it works:
ReadEntry.find_or_create_by_entry_id_and_user_id(entry.id, user.id, :read => false)
Logically, these methods should be identical, right? Thanks.
I've also had weird experiences with find_or_create. I would love it if it worked, but it seems inconsistent.
I'm currently having the same issue as you, and I think it may be due to calling find_or_create on an association as opposed to the model directly. Here's my example:
permission_assignments.find_or_create_by_role_id(:role_id => role_id, :is_allowed => false)
This works to create the assignment, except the "is_allowed" field gets set to it's default of "true". This code works for me (in the Permission model, hence the self reference)
PermissionAssignment.find_or_create_by_permission_id_and_role_id(:permission_id => self.id, :role_id => role_id, :is_allowed => false)
It's more verbose, unfortunately, but it works. The only problem that I still notice is that the object that is returned has no id assigned (the record does get created in the database, however, but if I wanted to update any more attributes I wouldn't be able to without the id). Don't know if that's a separate issue or not.
Rails 3.0.4 here with Postgres 8.4
You cannot pass in other fields like that as Rails will assume they are options for the find. Instead, you will need to make your method call longer:
user.read_entries.find_or_create_by_entry_id_and_read(entry.id, false)
Or alternatively use a shorter, custom syntax for that.
For your final example, my thoughts are that Rails will take the second argument and use that as options. Other than that, I am not sure.
I would like to see the SQL statement that a given ActiveRecord Query will generate. I recognize I can get this information from the log after the query has been issued, but I'm wondering if there is a method that can be called on and ActiveRecord Query.
For example:
SampleModel.find(:all, :select => "DISTINCT(*)", :conditions => ["`date` > #{self.date}"], :limit => 1, :order => '`date`', :group => "`date`")
I would like to open the irb console and tack a method on the end that would show the SQL that this query will generate, but not necessarily execute the query.
Similar to penger's, but works anytime in the console even after classes have been loaded and the logger has been cached:
For Rails 2:
ActiveRecord::Base.connection.instance_variable_set :#logger, Logger.new(STDOUT)
For Rails 3.0.x:
ActiveRecord::Base.logger = Logger.new(STDOUT)
For Rails >= 3.1.0 this is already done by default in consoles. In case it's too noisy and you want to turn it off you can do:
ActiveRecord::Base.logger = nil
Stick a puts query_object.class somewhere to see what type of object your working with, then lookup the docs.
For example, in Rails 3.0, scopes use ActiveRecord::Relation which has a #to_sql method. For example:
class Contact < ActiveRecord::Base
scope :frequently_contacted, where('messages_count > 10000')
end
Then, somewhere you can do:
puts Contact.frequently_contacted.to_sql
just use to_sql method and it'll output the sql query that will be run. it works on an active record relation.
irb(main):033:0> User.limit(10).where(:username => 'banana').to_sql
=> "SELECT "users".* FROM "users" WHERE "users"."username" = 'banana'
LIMIT 10"
when doing find, it won't work, so you'll need to add that id manually to the query or run it using where.
irb(main):037:0* User.where(id: 1).to_sql
=> "SELECT "users".* FROM "users" WHERE "users"."id" = 1"
This may be an old question but I use:
SampleModel.find(:all,
:select => "DISTINCT(*)",
:conditions => ["`date` > #{self.date}"],
:limit=> 1,
:order => '`date`',
:group => "`date`"
).explain
The explain method will give quite a detailed SQL statement on what its going to do
This is what I usually do to get SQL generated in console
-> script/console
Loading development environment (Rails 2.1.2)
>> ActiveRecord::Base.logger = Logger.new STDOUT
>> Event.first
You have to do this when you first start the console, if you do this after you have typed some code, it doesn't seem to work
Can't really take credit for this, found it long time ago from someone's blog and can't remember whose it is.
When last I tried to do this there was no official way to do it. I resorted to using the function that find and its friends use to generate their queries directly. It is private API so there is a huge risk that Rails 3 will totally break it, but for debugging, it is an ok solution.
The method is construct_finder_sql(options) (lib/active_record/base.rb:1681) you will have to use send because it is private.
Edit: construct_finder_sql was removed in Rails 5.1.0.beta1.
Create a .irbrc file in your home directory and paste this in:
if ENV.include?('RAILS_ENV') && !Object.const_defined?('RAILS_DEFAULT_LOGGER')
require 'logger'
RAILS_DEFAULT_LOGGER = Logger.new(STDOUT)
end
That will output SQL statements into your irb session as you go.
EDIT: Sorry that will execute the query still, but it's closest I know of.
EDIT: Now with arel, you can build up scopes/methods as long as the object returns ActiveRecord::Relation and call .to_sql on it and it will out put the sql that is going to be executed.
My typical way to see what sql it uses is to introduce a "bug" in the sql, then you'll get an error messages spit out to the normal logger (and web screen) that has the sql in question. No need to find where stdout is going...
Try the show_sql plugin. The plugin enables you to print the SQL without running it
SampleModel.sql(:select => "DISTINCT(*)", :conditions => ["`date` > #{self.date}"], :limit => 1, :order => '`date`', :group => "`date`")
You could change the connection's log method to raise an exception, preventing the query from being run.
It's a total hack, but it seems to work for me (Rails 2.2.2, MySQL):
module ActiveRecord
module ConnectionAdapters
class AbstractAdapter
def log_with_raise(sql, name, &block)
puts sql
raise 'aborting select' if caller.any? { |l| l =~ /`select'/ }
log_without_raise(sql, name, &block)
end
alias_method_chain :log, :raise
end
end
end
You can simply use to_sql() function with the active record
Form.where(status:"Active").to_sql
In Rails 3 you can add this line to the config/environments/development.rb
config.active_record.logger = Logger.new(STDOUT)
It will however execute the query. But half got answered :