Mongoid querying - ruby-on-rails-3

I have some application on RoR with Mongodb database. I use Mongoid mapper. Model post.rb
class Post
include Mongoid::Document
field :title, :type => String
field :text, :type => String
embeds_many :comments
end
Model comment.rb
class Comment
include Mongoid::Document
field :name, :type => String
field :content, :type => String
embedded_in :post, :inverse_of => :comments
end
In database this post with some comment has next structure:
{
"_id": ObjectId("4ecbeacf65430f0cef000003"),
"comments": {
"0": {
"name": "my name",
"content": "example content",
"_id": ObjectId("4ecbead365430f0cef000005")
},
"1": {
"name": "some name",
"content": "example content",
"_id": ObjectId("4ecbead665430f0cef000007")
},
"2": {
"name": "some name",
"content": "example content",
"_id": ObjectId("4ecbeada65430f0cef000009")
}
},
"text": "example text",
"title": "example title"
}
And, for example, in database was a few posts with my comments.
I need to find all posts, where "name": "my name", i.e. I need to find all editable by me posts.

It should appear as a array of comments instead of a hash. See my example below.
Also, as per the mongoid docs use the new style field declarations.
comment.rb:
class Comment
include Mongoid::Document
field :name, type: String
field :content, type: String
embedded_in :post
end
post.rb:
class Post
include Mongoid::Document
field :title, type: String
field :text, type: String
embeds_many :comments
end
Rails Console:
p = Post.new(:title => "title", :text => "post")
c1 = Comment.new(:name => "Tyler", :comment => "Awesome Comment!")
c2 = Comment.new(:name => "Joe", :comment => "comment 2")
p.comments << c1
p.comments << c2
p.save
Mongo Console:
> db.posts.findOne()
{
"_id" : ObjectId("4ecd151d096f762149000001"),
"title" : "title",
"text" : "post body",
"comments" : [
{
"name" : "Tyler",
"comment" : "Awesome Comment!",
"_id" : ObjectId("4ecd1569096f762149000002")
},
{
"name" : "Joe",
"comment" : "comment 2",
"_id" : ObjectId("4ecd157f096f762149000003")
}
]}
Then, to do the query you want, which I think was "comments posted by me?":
> db.posts.findOne({"comments.name": "Tyler"})
Also, I would add an index to the comments.name field:
> db.posts.ensureIndex({"comments.name": 1})

Related

How to get has_and_belongs_to_many relation objects together

I have HABTM associated tables like below.
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
end
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
end
How do I get User(s) with associated Groups together(nested), like below
{
id: 8,
name: "John",
groups: [
{
id: 17,
name: "Alpha Group",
user_id: 8
},
{
id: 18,
name: "Bravo Group",
user_id: 8
}
],
},
Is there a nice rails way of doing this?
I can manually create object like above but it would be nice to have a simpler query.
def index
#group = Group.find(params[:id])
respond_to do |format|
format.html
format.json { #group.users.map do |user|
user.to_json(:include => :groups)
end
}
end
end
It'll return an array like:
[
{
"id":1,
"email":"admin#example.com",
"groups":[
{
"id":1,
"name":"Yo"
},
{
"id":2,
"name":"Bro"
},
]
},
{
"id":2,
"email":"valar#example.com",
"groups":[
{
"id":1,
"name":"Arya"
},
{
"id":2,
"name":"Stark"
},
]
}
]

Autosave issue in rails 3.2

In the given code,
class Supplier < ActiveRecord::Base
has_one :criteria, foreign_key: "crt_sup_id", :autosave => true
self.primary_key = 'sup_id'
end
class Criteria < ActiveRecord::Base
belongs_to :supplier, foreign_key: "crt_sup_id"
self.primary_key = 'crt_id'
self.table_name = 'criterias'
end
autosave is not working when I am submitting the form. Supplier records are created but not Criteria.
Form code
class SupplierForm < Netzke::Basepack::Form
def configure(c)
c.model = 'Supplier'
super
c.items = [
{field_label: "Name", name: :bname},
{field_label: "Detail", name: :detail},
{
layout: :hbox, border: false, defaults: {border: false}, items: [
{
flex: 1,
layout: :anchor,
defaults: {anchor: "-8"},
items: [
{field_label: "Value 1", name: :criteria__val_one, xtype: :checkbox, nested_attribute: true},
{field_label: "Value 2", name: :criteria__val_two, xtype: :checkbox, nested_attribute: true}
]
}
]
}
]
end
end
Controller code
def index
end
Solved with the help of Netzke author. Replace criteria__val_one with criteria_val_one and
criteria__val_two with criteria_val_two. Create virtual attributes in the model class. Now all the values entered in the form is accessible with these virtual attributes and can be saved. Credit goes to Max Gorin. Thanks for the great work (Netzke)

Rails error with rspec: "expected valid? to return true, got false"

I'm new to rails, and after completing Michael Hartl's Ruby on Rails Tutorial, I am trying to expand some of the functionality. I started by changing the microposts so that they collect more data.
When running a rspec test, I can't figure out why I am getting this error:
$ bundle exec rspec spec/models/micropost_spec.rb
...............F.....
Failures:
1) Micropost
Failure/Error: it { should be_valid }
expected valid? to return true, got false
# ./spec/models/micropost_spec.rb:46:in `block (2 levels) in <top (required)>'
Finished in 1.57 seconds
21 examples, 1 failure
Failed examples:
rspec ./spec/models/micropost_spec.rb:46 # Micropost
micropost_spec.rb
require 'spec_helper'
describe Micropost do
let(:user) { FactoryGirl.create(:user) }
before { #micropost = user.microposts.build(content: "Lorem ipsum",
title: "This is a test title",
privacy: "1",
groups: "This is a test Group",
loc1T: "21 Bond St. Toronto, Ontario",
loc1Lat: "43.654653",
loc1Lon: "-79.377627",
loc2T: "21 Bond St. Toronto, Ontario",
loc2Lat: "43.654653",
loc2Lon: "-79.377627",
startTime: "Jan 1, 2000 12:01:01",
endTime: "Jan 2, 2000 12:01:01",
imageURL: "http://i.cdn.turner.com/cnn/.e/img/3.0/global/header/hdr-main.gif")
puts #micropost.errors.messages
}
#before { #micropost = user.microposts.build(title: "This is a test title") }
#before { #micropost = user.microposts.build(privacy: "1") }
#before { #micropost = user.microposts.build(groups: "This is a test Group") }
#before { #micropost = user.microposts.build(loc1T: "21 Bond St. Toronto, Ontario") }
#before { #micropost = user.microposts.build(loc1Lat: "43.654653") }
#before { #micropost = user.microposts.build(loc1Lon: "-79.377627") }
#before { #micropost = user.microposts.build(loc2T: "21 Bond St. Toronto, Ontario") }
#before { #micropost = user.microposts.build(loc2Lat: "43.654653") }
#before { #micropost = user.microposts.build(loc2Lon: "-79.377627") }
#before { #micropost = user.microposts.build(startTime: "Jan 1, 2000 12:01:01") }
#before { #micropost = user.microposts.build(endTime: "Jan 2, 2000 12:01:01") }
#before { #micropost = user.microposts.build(imageURL: "http://i.cdn.turner.com/cnn/.e/img/3.0/global/header/hdr-main.gif") }
subject { #micropost }
it { should respond_to(:content) }
it { should respond_to(:user_id) }
it { should respond_to(:user) }
it { should respond_to(:title) }
it { should respond_to(:privacy) }
it { should respond_to(:groups) }
it { should respond_to(:loc1T) }
it { should respond_to(:loc1Lat) }
it { should respond_to(:loc1Lon) }
it { should respond_to(:loc2T) }
it { should respond_to(:loc2Lat) }
it { should respond_to(:loc2Lon) }
it { should respond_to(:startTime) }
it { should respond_to(:endTime) }
it { should respond_to(:imageURL) }
its(:user) { should == user }
it { should be_valid }
describe "accessible attributes" do
it "should not allow access to user_id" do
expect do
Micropost.new(user_id: user.id)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
describe "when user_id is not present" do
before { #micropost.user_id = nil }
it { should_not be_valid }
end
describe "with blank content" do
before { #micropost.content = " " }
it { should_not be_valid }
end
describe "with content that is too long" do
before { #micropost.content = "a" * 141 }
it { should_not be_valid }
end
end
micropost.rb
class Micropost < ActiveRecord::Base
attr_accessible :content, :title,:privacy,:groups,:loc1T,:loc1Lat,:loc1Lon,:loc2T,:loc2Lat,:loc2Lon,:startTime,:endTime,:imageURL
belongs_to :user
validates :user_id, presence: true
validates :title, presence: true
validates :privacy, presence: true
validates :groups, presence: true
validates :loc1T, presence: true
validates :loc1Lat, presence: true
validates :loc1Lon, presence: true
validates :loc2T, presence: true
validates :loc2Lat, presence: true
validates :loc2Lon, presence: true
validates :startTime, presence: true
validates :endTime, presence: true
validates :imageURL, presence: true
validates :content, presence: true, length: { maximum: 140 }
default_scope order: 'microposts.created_at DESC'
def self.from_users_followed_by(user)
followed_user_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
where("user_id IN (#{followed_user_ids}) OR user_id = :user_id",
user_id: user.id)
end
end
factories.rb
FactoryGirl.define do
factory :user do
#name "Michael Hartl"
#email "michael#example.com"
sequence(:name) { |n| "Person #{n}" }
sequence(:email) { |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
factory :admin do
admin true
end
end
factory :micropost do
content "Lorem ipsum"
title "This is a test title"
privacy "1"
groups "This is a test Group"
loc1T "21 Bond St. Toronto, Ontario"
loc1Lat "43.654653"
loc1Lon "-79.377627"
loc2T "21 Bond St. Toronto, Ontario"
loc2Lat "43.654653"
loc2Lon "-79.377627"
startTime "Jan 1, 2000 12:01:01"
endTime "Jan 2, 2000 12:01:01"
imageURL "http://i.cdn.turner.com/cnn/.e/img/3.0/global/header/hdr-main.gif"
user
end
end
I'm not sure if there is anything else you need me to post.
It is not valid because your model object is not passing your validations. In your before block, after you create #micropost, add this line to see which validations are failing
puts #micropost.errors.messages
There will be a hash of validations (fields and error messages) that have failed. Fix those and then your object will be valid. Some earlier commenters had suggestions on where to start to help fix the problem.
This got rid of the errors.
micropost_spec.rb
describe Micropost do
let(:user) { FactoryGirl.create(:user) }
before { #micropost = user.microposts.build(content: "Lorem ipsum",
title: "This is a test title",
privacy: "1",
groups: "This is a test Group",
loc1T: "21 Bond St. Toronto, Ontario",
loc1Lat: "43.654653",
loc1Lon: "-79.377627",
loc2T: "21 Bond St. Toronto, Ontario",
loc2Lat: "43.654653",
loc2Lon: "-79.377627",
startTime: "Jan 1, 2000 12:01:01",
endTime: "Jan 2, 2000 12:01:01",
imageURL: "http://i.cdn.turner.com/cnn/.e/img/3.0/global/header/hdr-main.gif")
puts #micropost.errors.messages
}

Using has_key in array in rails from multi-level JSON data

I have a set of JSON data that has multiple levels of data. Snippet of the JSON (ignore the missing formatting please):
DATA SET A:
"interaction": {
"author": {
"username": "johndoe",
"name": "John Doe"
}
},
"gender": "male"
DATA SET B:
"interaction": {
"author": {
"name": "Jane Doe"
}
},
"gender": "male"
In a single-level I can use:
if record.has_key?('gender')
and that will return a true/false value if the key is present.
If I try to seed data and the key isn't present, it will throw an error and stop seeding.
My question: How would I check to see if the "username" key exists. Data Set B, for example, doesn't have a username key and would throw an error, but I can't figure out how to modify the has_key() command to check for a few levels down.
Thanks for the help.
I've decided to work around the has_key method and used a begin/rescue/end.
begin
#data.username = record['interaction']['author']['username']
rescue
#data.username = nil
end
Here's a possibility:
# where a and b are loaded from JSON
a = {
'interaction' => {
'author' => {
'username' => 'johndoe',
'name' => 'jd'
}
}
}
b = {
'interaction' => {
'author' => {
'oijoijoij' => 'johndoe',
'name' => 'jd'
}
}
}
class Hash
def recursive_has_key?(key)
has_key?(key) or values.any? { |v| v.is_a?(Hash) and v.recursive_has_key?(key) }
end
end
puts a.recursive_has_key?('username')
puts b.recursive_has_key?('username')
Which outputs
$ ruby foo.rb
true
false

Creating this array format

I need help in creating a json sting in the following format.
[{"key": "hello world", "value": "hello world"}, {"key": "movies", "value": "movies"}, {"key": "ski", "value": "ski"}, {"key": "snowbord", "value": "snowbord"}]
my input data will be categories with following attributes.
:id, :name, :active
and I would like to use the :id as key and :name as value.
input_data.map { |d| { "key" => d.id, "value" => d.name } }