I have the following structure:
class User
include Mongoid::Document
end
class Resource
include Mongoid::Document
embeds_many :permissions
end
class Permission
include Mongoid::Document
embedded_in :resource
field :read, type: Boolean
field :write, type: Boolean
field :user_id, type: BSON::ObjectId
end
Now let's suppose I have the following data:
1.9.3p194 :001 > a = User.create
=> #<User _id: 4ff46818f83222daf9000001, _type: nil>
1.9.3p194 :002 > b = User.create
=> #<User _id: 4ff4681bf83222daf9000002, _type: nil>
1.9.3p194 :003 > r = Resource.create
=> #<Resource _id: 4ff46822f83222daf9000003, _type: nil>
1.9.3p194 :004 > r.permissions.create(read: true, user_id: a.id)
=> #<Permission _id: 4ff46835f83222daf9000004, _type: nil, read: true, write: nil, user_id: 4ff46818f83222daf9000001>
1.9.3p194 :005 > r.permissions.create(read: true, write: true, user_id: b.id)
=> #<Permission _id: 4ff4684af83222daf9000005, _type: nil, read: true, write: true, user_id: 4ff4681bf83222daf9000002>
Now I want to find all Resources for which User A has write access (which should be none)
1.9.3p194 :007 > Resource.where('permissions.write' => true).where('permissions.user_id' => a.id).all.entries
=> [#<Resource _id: 4ff46822f83222daf9000003, _type: nil>]
This obviously fails because for each where clause there is a match.
What would be the correct solution for this? Is there a way to do an and for embedded document queries?
Try this
Resource.where(:permissions.matches => {:write => true, :user_id => a.id}).all
Try this one:
Resource.Permission.where(write: true,user_id: a.id)
Related
I have...
/app/models/input.rb:
class Input < ActiveRecord::Base
has_many :questions, :dependent => :destroy
after_commit :create_matching_questions
def create_matching_questions
#element_id = Element.all.select{|e| e.meta == true}.first.id
#standard_id = Standard.all.select{|s| s.meta == true}.first.id
#description = ["Does the site stock ", self.name, "?"].join
Product.all.each do |product|
#question = product.questions.find_or_create_by_element_id_and_standard_id_and_description!(#element_id, #standard_id, #description)
self.questions << #question
#question.fields.find_or_create_by_name("The site sells this product and it is in stock")
#question.fields.find_or_create_by_name("The site sells this product but it is not in stock")
#question.fields.find_or_create_by_name("The site does not sell this product")
#question.update_attributes :active => true
end
return true
end
end
/app/models/question.rb:
class Question < ActiveRecord::Base
belongs_to :input
after_commit :create_matching_surveys
def create_matching_surveys
if self.active == true
self.reload.product.reviews.each do |review|
review.competitors.each do |competitor|
(1..self.iterations).each do |iteration|
survey = competitor.surveys.find_or_create_by_question_id_and_iteration!(self.id, iteration)
survey.save
end
end
end
return true
else
self.destroy_matching_surveys
end
end
def destroy_matching_surveys
self.surveys.each do |survey|
survey.destroy if survey.question_id == self.id
end
return true
end
end
Why, then, do I get...
> #finance = Good.create! :name => "Finance"
=> #<Good id: 6, name: "Finance", created_at: "2013-06-13 02:56:20", updated_at: "2013-06-13 02:56:20">
> #super = Input.create! :name => "Superannuation"
=> #<Input id: 11, name: "Superannuation", mispelling: nil, typo: nil, created_at: "2013-06-13 02:56:28", updated_at: "2013-06-13 02:56:28">
> #first = #super.questions.first
=> #<Question id: 48, standard_id: 1, description: "Does the site stock Superannuation?", element_id: 2, condition_id: nil, blueprint_name: nil, blueprint_url: nil, additive: false, instructions: nil, created_at: "2013-06-13 02:56:41", updated_at: "2013-06-13 02:56:41", active: false, postscript: "<p>If you have any comments about this question or ...", iterations: 1, product_id: 1, precondition_id: nil, input_id: 11>
> #last = #super.questions.last
=> #<Question id: 60, standard_id: 1, description: "Does the site stock Superannuation?", element_id: 2, condition_id: nil, blueprint_name: nil, blueprint_url: nil, additive: false, instructions: nil, created_at: "2013-06-13 02:56:43", updated_at: "2013-06-13 02:56:43", active: false, postscript: "<p>If you have any comments about this question or ...", iterations: 1, product_id: 23, precondition_id: nil, input_id: 11>
> #super.destroy
=> #<Input id: 11, name: "Superannuation", mispelling: nil, typo: nil, created_at: "2013-06-13 02:56:28", updated_at: "2013-06-13 02:56:28">
> #super.destroyed?
=> true
> #first.destroyed?
=> false
> #last.destroyed?
=> false
Surely #first and #last should be destroyed automatically?
I had the same problem, solved it by :dependent => :delete_all instead of :dependent => :destroy.
:delete_all doesn't call the destroy method from your controller and delete data directly from your database.
I have the following model:
class Party < ActiveRecord::Base
has_many :party_characters
has_many :characters, :through => :party_characters
...
And on my controller I have the following code:
def new
#party = Party.new
p #party.characters.any?
p #party.characters
p #party.characters.any?
...
That writes the following to the console:
true
[]
false
Why does the any? method returns true before the print and false after?
Tried the same thing on the console and got the following results
1.9.3p0 :003 > p = Party.new
=> #<Party id: nil, name: nil, created_at: nil, updated_at: nil>
1.9.3p0 :004 > p.characters.any?
(17.9ms) SELECT COUNT(*) FROM "characters" INNER JOIN "party_characters" ON "characters"."id" = "party_characters"."character_id" WHERE "party_characters"."party_id" IS NULL
=> true
1.9.3p0 :005 > p.characters
=> []
1.9.3p0 :006 > p.characters.any?
=> false
So I decided to check what I had on the party_characters table and found entries with a valid character_id but with a nil party id. I removed those and everything works as supposed.
Here a tricky issue dealing with nested documents, references and embedded documents.
You can find a zip file containing a sample project illustrating the issue.
I'm Using
Rails v3.0.9
Mongo v1.8.3
Mongoid v2.2.0
Models
Conversation
Message
Text
Media
Link
A Conversation embeds_many Messages (autosave => true)
A Message has_many Media (autosave => true)
Text inherit from Message (STI)
Link inherit from Link (STI)
A Conversation accept_nested_attributes_for :messages
A Message accept_nested_attributes_for :medias
In rails console, let's do some testing :
ruby-1.9.2-p180 :001 > c = Conversation.new({:messages_attributes => [{'_type' => 'Text', :content => 'content', :medias_attributes => [{'_type' => 'Link', :url => 'google.com'}]}]})
=> #<Conversation _id: 4e65c7275d156d0129000001, _type: nil, created_at: nil, updated_at: nil, title: nil>
ruby-1.9.2-p180 :002 > c.messages
=> [#<Message _id: 4e65c7275d156d0129000002, _type: "Text", created_at: nil, updated_at: nil, content: "content">]
ruby-1.9.2-p180 :003 > c.messages.first.medias
=> [#<Media _id: 4e65c7275d156d0129000003, _type: "Link", created_at: nil, updated_at: nil, message_id: BSON::ObjectId('4e65c7275d156d0129000002'), url: "google.com">]
ruby-1.9.2-p180 :004 > c.save
=> true
ruby-1.9.2-p180 :005 > c.reload
=> #<Conversation _id: 4e65c7275d156d0129000001, _type: nil, created_at: 2011-09-06 07:09:33 UTC, updated_at: 2011-09-06 07:09:33 UTC, title: nil>
ruby-1.9.2-p180 :006 > c.messages
=> [#<Text _id: 4e65c7275d156d0129000002, _type: "Text", created_at: nil, updated_at: nil, content: "content">]
ruby-1.9.2-p180 :007 > c.messages.first.medias
=> [] #Should not be empty
Now if you change the relations between Conversation <=> Message as follow :
A Conversation has_many Messages
It works perfectly.
Some rails console testing also :
ruby-1.9.2-p180 :002 > c = Conversation.new({:messages_attributes => [{'_type' => 'Text', :content => 'content', :medias_attributes => [{'_type' => 'Link', :url => 'google.com'}]}]})
=> #<Conversation _id: 4e65c6bb5d156d011a000004, _type: nil, created_at: nil, updated_at: nil, title: nil>
ruby-1.9.2-p180 :003 > c.messages
=> [#<Message _id: 4e65c6bb5d156d011a000005, _type: "Text", created_at: nil, updated_at: nil, conversation_id: BSON::ObjectId('4e65c6bb5d156d011a000004'), content: "content">]
ruby-1.9.2-p180 :004 > c.messages.first.medias
=> [#<Media _id: 4e65c6bb5d156d011a000006, _type: "Link", created_at: nil, updated_at: nil, message_id: BSON::ObjectId('4e65c6bb5d156d011a000005'), url: "google.com">]
ruby-1.9.2-p180 :005 > c.save
=> true
ruby-1.9.2-p180 :006 > c.reload
=> #<Conversation _id: 4e65c6bb5d156d011a000004, _type: nil, created_at: 2011-09-06 07:07:54 UTC, updated_at: 2011-09-06 07:07:54 UTC, title: nil>
ruby-1.9.2-p180 :007 > c.messages
=> [#<Text _id: 4e65c6bb5d156d011a000005, _type: "Text", created_at: 2011-09-06 07:07:54 UTC, updated_at: 2011-09-06 07:07:54 UTC, conversation_id: BSON::ObjectId('4e65c6bb5d156d011a000004'), content: "content">]
ruby-1.9.2-p180 :008 > c.messages.first.medias
=> [#<Link _id: 4e65c6bb5d156d011a000006, _type: "Link", created_at: 2011-09-06 07:07:54 UTC, updated_at: 2011-09-06 07:07:54 UTC, message_id: BSON::ObjectId('4e65c6bb5d156d011a000005'), url: "google.com">]
Here is the link to the sample zip file : http://d.pr/oMUc
Well, not so easy on this one ^^
However here is the link to the git issue for mongoid : https://github.com/mongoid/mongoid/issues/1216
For people who have the same issue !
I'm trying to use Rails(2)'s to_json model serializing mechanism to emit some data from associated models. As my guide I'm referencing the following essentially identical documentation URLs:
http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
http://apidock.com/rails/ActiveRecord/Serialization/to_json
Here is some of the relevant model code:
class WorkEffortAssignment < ActiveRecord::Base
belongs_to :work_effort
belongs_to :assigned_to, :polymorphic => true
belongs_to :assigned_by, :polymorphic => true
Here is the controller code, I'm just trying to dump some JSON for initial testing purposes:
def dump_work_effort_assignments
WorkEffort.include_root_in_json = false
all_assignments = WorkEffortAssignment.all
options = {:include => [:work_effort, :assigned_to, :assigned_by], :only => [:work_effort_id, :assigned_to_id, :assigned_by_id]}
ext_json = "{data:#{WorkEffortAssignment.all.to_json(options)}}"
render :inline => ext_json
end
Here's the first record of Json data with empty hashes for work_effort, assigned_to and assigned_by:
{data:[{"assigned_to":{},"work_effort_id":"9","assigned_to_id":3,"assigned_by":{},"work_effort":{},"assigned_by_id":3}, //etcetera
But below is my console session showing the associations that I'd like to represent in my Json. So what am I doing wrong in my controller when trying to specify the include option for to_json, such that I can easily send associated model data back to the browser. Thanks in advance
>> assignment = WorkEffortAssignment.first
=> #<WorkEffortAssignment id: 1, assigned_at: nil, assigned_from: nil, assigned_
thru: nil, unassigned_at: nil, assigned_to_id: 3, assigned_to_type: "Party", ass
igned_by_id: 3, assigned_by_type: "Party", created_at: nil, updated_at: nil, wor
k_effort_id: "9">
>> assignment.work_effort
=> #<WorkEffort id: 9, description: "Software Architecture Document", type: nil,
started_at: nil, finished_at: nil, projected_completion_time: nil, actual_compl
etion_time: nil, created_at: "2011-08-18 13:39:30", updated_at: "2011-08-25 13:3
9:30", facility_id: nil, facility_type: nil, work_effort_record_id: nil, work_ef
fort_record_type: nil, projected_cost_id: nil, actual_cost_id: nil, parent_id: n
il, lft: 1, rgt: 2>
>> assignment.assigned_to
=> #<Party id: 3, description: "George Jempty", business_party_id: 2, business_p
arty_type: "Individual", list_view_image_id: nil, enterprise_identifier: nil, cr
eated_at: "2011-08-29 14:21:41", updated_at: "2011-08-29 14:21:41">
>> assignment.assigned_by
=> #<Party id: 3, description: "George Jempty", business_party_id: 2, business_p
arty_type: "Individual", list_view_image_id: nil, enterprise_identifier: nil, cr
eated_at: "2011-08-29 14:21:41", updated_at: "2011-08-29 14:21:41">
The :only specifier you have there is throwing it off. The following will include the child-objects:
def dump_work_effort_assignments
WorkEffort.include_root_in_json = false
all_assignments = WorkEffortAssignment.all
options = {:include => [:work_effort, :assigned_to, :assigned_by]}
ext_json = "{data:#{WorkEffortAssignment.all.to_json(options)}}"
render :inline => ext_json
end
...but will not filter out the attributes of the parent. If you want to do that, it may be simpler to just build a hash and convert it to json:
def dump_work_effort_assignments
data = WorkEffortAssignment.all.map {|wea| {
:work_effort => wea.work_effort,
:assigned_to => wea.assigned_to,
:assigned_by => wea.assigned_by
}}
ext_json = "{data:#{data.to_json}}"
render :inline => ext_json
end
The problem I am having with this is Product is trying to create variants before the product is even created and there are certain callbacks for variants that require the product to exist. So how can I rewrite this so that v.save doesn't execute till the object is created or whatever.
Product.class_eval do
validates_presence_of [:style_no, :market_price, :designer, :short_description, :description]
validates_numericality_of [:size_47_quantity,
:size_46_quantity,
:size_45_quantity,
:size_44_quantity,
:size_43_quantity,
:size_42_quantity,
:size_41_quantity,
:size_40_quantity,
:size_39_quantity]
for i in 39..47
define_method:"size_#{i}_quantity" do
if v = self.variants.find_by_size(i)
v.count_on_hand
else
0
end
end
define_method:"size_#{i}_quantity=" do |amount|
# if only there is some method that can postpone all the following if this product hasn't been created yet!
self.id = Product.last.id + 1 unless self.id
v = self.variants.find_by_size(i) || self.variants.new(:size => i)
v.count_on_hand = amount
v.save
end
end
end
You can try this solution:
Product class
class Product < ActiveRecord::Base
validates_presence_of [:style_no, :market_price, :designer, :short_description, :description]
has_many :variants
# This method would check if variant was created or loaded.
#
# So many sequantial calls to it will return same object
def variant_with_size(size)
self.variants.select{|v| v.size == size}.first || self.variants.where('size = ?', size).first
end
module ClassExtensions
def self.included(base)
(39..47).each do |i|
method = "size_#{i}_quantity".to_sym
included_module = Module.new
included_module.module_eval <<EOF
def #{method}
if v = self.variant_with_size(#{i})
v.count_on_hand
else
0
end
end
def #{method}=(amount)
v = self.variant_with_size(#{i}) || self.variants.build(:size => #{i})
v.count_on_hand = amount
v
end
EOF
base.send :include, included_module
end
end
end
include ClassExtensions
end
Variant class
class Variant < ActiveRecord::Base
belongs_to :product
validates :count_on_hand, :numericality => true
end
Usage
Usage example with correct variant amount:
ruby-1.9.2-p180 :001 > p = Product.new
=> #<Product id: nil, style_no: nil, market_price: nil, designer: nil, short_description: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :002 > p.size_39_quantity
=> 0
ruby-1.9.2-p180 :003 > p.size_39_quantity = 2
=> 2
ruby-1.9.2-p180 :004 > p.variants
=> [#<Variant id: nil, product_id: nil, size: 39, count_on_hand: 2, created_at: nil, updated_at: nil>]
ruby-1.9.2-p180 :005 > p.save
=> true
ruby-1.9.2-p180 :006 > p.variants
=> [#<Variant id: 3, product_id: 3, size: 39, count_on_hand: 2, created_at: "2011-04-06 06:34:46", updated_at: "2011-04-06 06:34:46">]
Usage with incorrect variant amount:
ruby-1.9.2-p180 :007 > p1 = Product.new
=> #<Product id: nil, style_no: nil, market_price: nil, designer: nil, short_description: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :008 > p1.size_39_quantity = 'A'
=> "A"
ruby-1.9.2-p180 :009 > p1.save
=> false
ruby-1.9.2-p180 :010 > p1.errors
=> {:variants=>["is invalid"]}
ruby-1.9.2-p180 :011 > p1.variants[0].errors
=> {:count_on_hand=>["is not a number"]}
At a glance, I'd consider using an after_save callback on Product to create product variants.
Something like:
class Product < ActiveRecord::Base
has_many :variants
after_save :create_variants! if :not_a_variant?
OPTIONS = [:size_1_qty, :size_2_qty] # TODO: move to a OptionType model associated with Product
def not_a_variant?
size.nil? # or however you might distinguish a Product from a Variant
end
private
def create_variants!
# OPTIONS could instead be related option_types. perhaps a 'size' option type with values of 40, 41, 42, etc.
OPTIONS.each do |size|
variants.build(...)
end
save!
end
end
I was just reviewing the Spree shopping cart project by Rails Dog and they handle product variants in a similar fashion. You might check it out.