Mongoid: If possible, how to assign a parent to pre-existing child? - sinatra

In a model definition can you have more than one belongs_to statement? If the answer is no, read no further. I am trying to create three 1-n referenced relationships with mongoid in a sinatra app.
models
class SkillTrack
include Mongoid::Document
belongs_to :student
belongs_to :grading_period
belongs_to :teacher
end
class Student
include Mongoid::Document
field :name
field :nickname
field :dob, type: Date
has_many :skill_tracks
end
class GradingPeriod
include Mongoid::Document
field :school_year
field :period_name
field :signing_date, type: Date
has_many :skill_tracks
end
class Teacher
include Mongoid::Document
field :name
has_many :skill_tracks
end
routes
post "/skill_track/new" do
form = params[:formdata] # using sinatra form helpers gem
student = Student.find("#{formdata["student_mongo_id"]}")
working = (student.skill_tracks.create).id
??? what do I do with working to make it a child of a teacher and of a grading_period?
end
what I have tried
The thing that looked the most promising to me from the mongoid docs was:
band.member_ids = [ id ] #Set the related document ids.
I mucked about in irb and tried lots of variations in my models but I could not set a parent teacher or grading period for the newly created skilltracking object. The student foreign key was set properly on creation.
I am hoping I have a simple syntax ignorance, but I tried so many variations I wonder if I can do this at all.
UPDATE:
I just needed to add working.save to David Troyer answer and boom working.
post "/skill_track/new" do
form = params[:formdata]
student = Student.find("#{formdata["student_mongo_id"]}")
working = student.skill_tracks.create
working.teacher = Teacher.create # or find
working.grading_period = GradingPeriod.create # or find
working.save
end

I believe so. If I understand your question correctly, try using some setters on the child SkillTrack document.
post "/skill_track/new" do
form = params[:formdata]
student = Student.find("#{formdata["student_mongo_id"]}")
working = student.skill_tracks.create
working.teacher = Teacher.create # or find
working.grading_period = GradingPeriod.create # or find
end
Dig a little bit further into the Operations section of the mongoid docs you referenced

Related

Ruby on Rails - PostgreSQL Grouping error when doing joins & group

In my application I have models Visit & Post.
class Post < ActiveRecord::Base
has_many :visits
class Visit < ActiveRecord::Base
belongs_to :post, :counter_cache => true
Im trying to get all visits a post has in visits table. I did:
- a = Visit.joins(:post).group(:post_id).select(:post_id, :title, 'count(visits.id) as total_views').where(user: current_user)
- a.each do |a|
%tr
%td= a.title
%td= a.total_views
This works find in my development env/localhost (I think since I use sqlite3), butI am using PostgreSql in my production and I got this error:
PG::GroupingError: ERROR: column "posts.title" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: ...ECT count(visits.id) as total_views, "visits"."post_id", "title", c...
What am I doing wrong and how to fix it?
As the answer says "posts.title" must appear in the GROUP BY. In group add :title along with :post_id
Visit.joins(:post).group([:post_id, :title]).select('sum(cpc_bid) as earnings', :post_id, :title, 'count(visits.id) as total_views').where(influencer: current_user)

MongoMapper issue with stack level being too deep

Just want to start off by saying I did google this topic at length and was unable to find anything that applied to my own use case.
I have a simple little ad server. There is a model for Ad, and two embedded models called Impression and Click - like so:
class Ad
include MongoMapper::Document
key :name, String
key :image, String
key :url, String
has_many :clicks
has_many :impressions
end
class Click
include MongoMapper::EmbeddedDocument
key :ip, String
timestamps!
end
class Impression
include MongoMapper::EmbeddedDocument
key :ip, String
timestamps!
end
And here is the error I'm getting:
SystemStackError - stack level too deep:
/home/deployer/.rbenv/versions/1.9.3-p125/lib/ruby/gems/1.9.1/gems/mongo_mapper-0.12.0/lib/mongo_mapper/plugins/keys.rb:194
Here is the area that this is happening in:
#ad.impressions << Impression.new({:ip => request.ip})
#ad.save
Now, I do not have any callbacks here in my models, which is the reason this error happens for a lot of people.
Anyone have any insights?
Thanks.
I seem to have hit on the answer. the timestamps! is what was causing this. The other examples I saw online had to do with callbacks and the way that ActiveSupport runs them. I just didn't realize that timestamps! counts as one.

In CREATE, init with params does fill all attributes

I am using RoR 3.1 + Postgres on MacOSX
In my create function I have this:
def create
#power_plant_substrate = PowerPlantSubstrate.new(params[:power_plant_substrate]) <= 1
#trade = params[:power_plant_substrate][:trade]
respond_to do |format|
if #power_plant_substrate.save
...
end
(1) should instantiate a new object with params[:power_plant_substrate]
THE PROBLEM:
Right after the creation of my new object #power_plant_substrate some of the attribute are available.
if I check params[:power_plant_substrate] value (trace to console) I can verify that all fields were passed correctly:
{"power_plant_id"=>"161", "substrate_id"=>"213", "quantity"=>"1", "periodicity"=>"yearly", "trade"=>"wanted", "price_per_unit"=>"0.00", "total_price"=>"0.00", "currency"=>"USD", "address"=>", Reserved", "transport"=>"pickup_only", "description"=>"afewrqe", "latitude"=>"", "longitude"=>""}
However I checked my object right after saving (#power_plant_substrate.save). "trade" attribute is not assigned anymore.
I tried accessing the same attributes in the model in a method that I call after_create, and same problem.
However, the record is available with all fields correctly assigned in the database.
WHY #power_plant_substrate object doesn't appear "fully" assigned after saving?
Hope you can help.
My guess is that there is a list of attr_accessible in PowerPlantSubstrate model. check if currency and trade are added in that list. If not, then add those.
attr_accessible :currency, :trade, . . .
Making a few attributes alone as attr_accessible will make it impossible to mass assign other variables. That is why individual assignment worked
If there is not list, try adding
attr_accessible nil
and see if that works

Embedded and no-embedded document in the same time

I need to have a model which will behave like a embedded and not-embedded.
For example if I want to store this model as embedded:
class MenuPosition
include Mongoid::Document
field :name, type: String
field :category, type: String
I need to add
embedded_in :menu
to it.
On the other side, if I add this line in the model I cannot store this model as not-embedded:
position = {
"name" => "pork",
"category" => "meal",
"portion" => 100
}
MenuPosition.create(position)
error message:
NoMethodError:
undefined method `new?' for nil:NilClass
Can I use one model for embedded and not-embedded documents?
In our project we had a similar thing. What we did is define the fields as a module. A bit like this:
module SpecialFields
extend ActiveSupport::Concern
included do
field :my_field, type: String
field :my_other_field, type: String
end
end
Then in your class where you want to embed, just do:
include SpecialFields
In your class that you'd like to store separately as a non-embedded document, do this:
class NotEmbeddedDoc
include Mongoid::Document
include SpecialFields
end
This worked pretty well in our project for a few things. However, it might not be appropriate in your case since you want to embed many. This only really works for embeds one cases I think. I have posted it here in case it helps people.

Mongo ids leads to scary URLs

This might sound like a trivial question, but it is rather important for consumer facing apps
What is the easiest way and most scalable way to map the scary mongo id onto a id that is friendly?
xx.com/posts/4d371056183b5e09b20001f9
TO
xx.com/posts/a
M
You can create a composite key in mongoid to replace the default id using the key macro:
class Person
include Mongoid::Document
field :first_name
field :last_name
key :first_name, :last_name
end
person = Person.new(:first_name => "Syd", :last_name => "Vicious")
person.id # returns "syd-vicious"
If you don't like this way to do it, check this gem: https://github.com/hakanensari/mongoid-slug
Define a friendly unique field (like a slug) on your collection, index it, on your model, define to_param to return it:
def to_param
slug
end
Then in your finders, find by slug rather than ID:
#post = Post.where(:slug => params[:id].to_s).first
This will let you treat slugs as your effective PK for the purposes of resource interaction, and they're a lot prettier.
Unfortunately, the key macro has been removed from mongo. For custom ids,
users must now override the _id field.
class Band
include Mongoid::Document
field :_id, type: String, default: ->{ name }
end
Here's a great gem that I've been using to successfully answer this problem: Mongoid-Slug
https://github.com/digitalplaywright/mongoid-slug.
It provides a nice interface for adding this feature across multiple models. If you'd rather roll your own, at least check out their implementation for some ideas. If you're going this route, look into the Stringex gem, https://github.com/rsl/stringex, and acts_as_url library within. That will help you get the nice dash-between-url slugs.