Ruby Datamapper: retrieving record using param in url path returns null - sometimes - ruby

I'm creating a Sinatra App using Datamapper.
With the following route, I'm attempting to print the record for an id. So localhost:9292/api/1 should return results for id=1
inside
get '/api/:id' do
I tried a couple things with varied results:
thing = Thing.get(params[:id])
thing.to_json
end
outputs 'null', but:
id_param = params[:id]
id_param
end
prints 1 as expected, and:
hardcoded_thing = Thing.get(1)
hardcoded_thing.to_json
end
correctly prints the hardcoded db record with id=1. So I must be losing it..
Any ideas?
Thanks!
For reference, here's my model:
class Thing
include DataMapper::Resource
include BCrypt
property :id, Serial, :key => true
property :created_at, DateTime
property :updated_at, DateTime
property :name, String, :length => 50
property :cafe_topic, Text
end

Try this:
get '/api/:id' do |id|
thing = Thing.get(id)
thing.to_json
end

Related

Why won't my DataMapper record save when I pass it the user it belongs to?

I have set up a simple has-many and belongs-to association using DataMapper with Sinatra. My User model has many 'peeps', and my Peep model belongs to user. See below for the classes....
I am able to successfully create a new peep which belongs to a particular user, by passing the user_id directly into the peep on initialization, like this:
Method 1
new_peep = Peep.create(content: params[:content], user_id: current_user.id)
This adds 1 to Peep to Peep.count.
However, my understanding is that I should be able to create the association by assigning the current_user to new_peep.user. But when I try that, the peep object won't save.
I've tried this:
Method 2
new_peep = Peep.create(content: params[:content], user: current_user)
Current user here is User.get(session[:current_user_id])
The resulting new_peep has an id of nil, but does have user_id set to the current_user's id. New_peep looks exactly like the new_peep that successfully gets created using Method 1, except it has no id because it hasn't successfully saved. I've tried calling new_peep.save separately, but I still get the below for the peep object:
<Peep #id=nil #content="This is a test peep" #created_at=#<DateTime: 2016-05-08T12:42:52+01:00 ((2457517j,42172s,0n),+3600s,2299161j)> #user_id=1>, #errors={}
Note that there are no validation errors. Most problems other people seem to have had with saving records come down to a validation criteria not being met.
I assumed this was something to do with the belongs_to association not working, but I can (after creating new_peep using Method 1 above) still call new_peep.user and access the correct user. So it seems to me the belongs_to is working as a reader but not a setter.
This problem also means I cannot create a peep by adding one into the user.peeps collection then saving user, which means there's virtually no point in peep belonging to user.
I've seen other people have had problems saving records that don't have any changes to save - but this is a completely new record, so all its attributes are being updated.
I'd really like to know what's going on - this has baffled me for too long!
Here are my classes:
class Peep
include DataMapper::Resource
property :id, Serial
property :content, Text
property :created_at, DateTime
belongs_to :user, required: false
def created_at_formatted
created_at.strftime("%H:%M, %A %-d %b %Y")
end
end
class User
include DataMapper::Resource
include BCrypt
attr_accessor :password_confirmation
attr_reader :password
property :id, Serial
property :email, String, unique: true, required: true
property :username, String, unique: true, required: true
property :name, String
property :password_hash, Text
def self.authenticate(params)
user = first(email: params[:email])
if user && Password.new(user.password_hash) == params[:password]
user
else
false
end
end
def password=(actual_password)
#password = actual_password
self.password_hash = Password.create(actual_password)
end
validates_confirmation_of :password
validates_presence_of :password
has n, :peeps
end
When you create the Peep you don't create it through User, maybe that's why it has no primary id, since it belongs to User. Also you're assigning it the foreign key user_id as if you have a property defined as such. Although the database has it, in DataMapper you don't pass in the foreign key id, it does it for you.
Try replacing
new_peep = Peep.create(content: params[:content], user: current_user)
with:
new_peep = current_user.peeps.create(content: params[:content], created_at: Time.now)

Create JSON from 2 associated Datamapper models

Here is my question.
I have 2 associated Datamapper models:
class Task
include DataMapper::Resource
property :id, Serial
property :date, Date
property :amount, Float
belongs_to :project, :required => true
end
class Project
include DataMapper::Resource
property :id, Serial
property :name, String, :required => true
property :desc, Text
belongs_to :company
has n, :tasks
end
My goal is to created JSON that will contain task date, amount and project name, that should be matched by project_id. At the moment JSON generation has following look:
Task.all.to_json(:only => [:date, :amount, :project_id])
I can access project_id from Task model, but have no idea how to add respective project name from Project model for every task. In SQL it looks like join:
select tasks.date, tasks.amount, projects.name from tasks
inner join projects
on tasks.project_id = projects.id;
Can you suggest correct way to create final JSON, using Datamapper way, but not SQL?
Thank you.
I have found solution for my problem. Here it is:
# create new structure to store merged result
Task_entry = Struct.new(:date, :amount, :pname)
# array to get results from database
all_task_items = Array.new
# run through result and fill the array with required data
Task.all.each do |task|
task_item = Task_entry.new(task.date, task.amount, task.project.name)
all_task_items << task_item
end
all_task_items.to_json # generate json
It works for me well. Hope it can be helpful.

Datamapper "Update" does not update record

I am having trouble with datamapper not updating a model. I can create and save models without issue. I have enabled raise_on_save_failure and checked the return value of update but see no errors.
Here is the model:
class UserProfile
include DataMapper::Resource
attr_accessor :id, :wants_hints, :is_beta_user
property :id, Serial #auto-increment integer key
property :is_beta_user, Boolean
property :wants_hints, Boolean
has 1, :user, :through => Resource
end
And here is where it is updated in the controller:
if user = User.get(request.session[:user])
if request.params[:user_profile]
beta = request.params[:user_profile].has_key?('is_beta_user')
hints = request.params[:user_profile].has_key?('wants_hints')
user.user_profile.update({:is_beta_user => beta, :wants_hints => hints}) # returns true
Log.puts user.user_profile.errors.each {|e| Log.puts e.to_s} # returns empty list []
end
end
When the controller is called update always returns true, and there are never errors in the error list. The datamapper log, which is set to :debug, only shows the SELECT queries for retrieving the user and user_profile and that is all. Why would I be able to save a newly created model, but not be allowed to update that same model?
Removing attr_accessor fixed the problem. From my research attr_accessor is used for attributes not in the database.
DataMapper's save and update do not necessarily produce an UPDATE sentence. It will only do so if the data held by the model object has changed. So, for example, in the following code the update will return true but will not produce an UPDATE:
# This generates an INSERT
user = User.create(:login => 'kintaro', :email => 'kintaro#example.com')
# This does NOT generate an UPDATE
user.update(:login => 'kintaro')
If you do this, however, an UPDATE will be produced:
# This generates an UPDATE
user.update(:login => 'kintaro22')
Maybe this is what's happening?

Mongomapper embedded document "Cannot serialize an object" error

I'm quite new to mongodb and I'm using sinatra and mongomapper to update the values of an embedded document with the following set up:
class TeamMember
include MongoMapper::Document
key :name, String, :required => true
many :team_member_projects
end
class TeamMemberProject
include MongoMapper::EmbeddedDocument
key :date, Date, :required => true
one :project
end
class Project
include MongoMapper::Document
key :name, String, :required => true
end
The modifier code is:
team_member = TeamMember.find(params[:team_member])
project = Project.find(params[:project])
date = Date.parse(params[:date])
tm_project = TeamMemberProject.new(:project => project, :date => date)
team_member.push(:team_member_projects => tm_project)
team_member.save
but I get the error for .push line:
BSON::InvalidDocument at /project/add
Cannot serialize an object of class TeamMemberProject into BSON.
Did I not declare my embedded document properly? Or is there another way to update embedded documents, I don't know about. I'm trying to use: http://mongomapper.com/documentation/plugins/modifiers.html#push
This seems to work
team_member = TeamMember.find(params[:team_member])
project = Project.find(params[:project])
date = Date.parse(params[:date])
tm_project = TeamMemberProject.new(:project_id => project.id, :date => date)
team_member.team_member_projects << tm_project
team_member.save
It seems like I have to use project.id. Not sure why. Also not sure why my .push doesn't work, as I would have assumed it does the same thing as <<.

Chained aggregate call across association in DataMapper (ruby)

I am working on a simple budget app using Sinatra and DataMapper in Ruby.
I want to get the sum of all transactions across all income accounts within the last 30 days.
Something like Account.income_accounts.account_entries.sum(:amount, :transaction_date.gte => Date.today - 30) should work. Instead, the limiting condition on transaction_date is getting ignored, returning the sum of the amount for all entries for all income accounts.
Given the following:
class Account
include DataMapper::Resource
has n, :account_entries
property :id, Serial
property :name, String
property :acct_type, String
def self.income_accounts
all(:acct_type => 'Income')
end
end
class AccountEntry
include DataMapper::Resource
belongs_to :account
property :id, Serial
property :account_id, Integer
property :description, String
property :amount, BigDecimal
property :transaction_date, DateTime
end
I am properly requiring dm-aggregates. I am new to DataMapper. If it matters, I am using a sqlite3 database. I really don't want to resort to using ruby to sum the results. It also feels wrong to resort to executing raw SQL for this type of simple aggregate query.
Can anyone shed some light on this? I would love to be pointed in the right direction regarding chained finders in DataMapper, particularly with aggregates. My spelunking into the API and the DataMapper site hasn't yielded a solution as of yet.
I just wrote a small stand-alone script to test your example, and it appears to return the correct results. Please note I am using edge extlib, dm-core, and dm-more all installed from git:
#!/usr/bin/env ruby -Ku
# encoding: utf-8
require 'rubygems'
require 'dm-core'
require 'dm-aggregates'
DataMapper::Logger.new($stdout, :debug)
DataMapper.setup(:default, 'sqlite3::memory:')
class Account
include DataMapper::Resource
property :id, Serial
property :name, String
property :acct_type, String
has n, :account_entries
def self.income_accounts
all(:acct_type => 'Income')
end
end
class AccountEntry
include DataMapper::Resource
property :id, Serial
property :description, String
property :amount, BigDecimal
property :transaction_date, Date
belongs_to :account
end
DataMapper.auto_migrate!
account = Account.create(
:name => 'Test Account',
:acct_type => 'Income'
)
5.times do |n|
account.account_entries.create(
:description => "Account Entry #{n}",
:amount => 1.00,
:transaction_date => Date.today
)
end
puts Account.income_accounts.account_entries(:transaction_date.gte => Date.today - 30).sum(:amount).to_s('F') # => 5.0
Can you run the above program and let me know what it returns for you? If you get something other than 5.0, try updating to the latest packages and retry.
DateTime uses second as it's base unit Date.today - 30 is 30 seconds ago. Try Date.today - 30.days
Did you try DateTime.now-30 or maybe even Time.now-30*3600*24 instead of Date.today-30 ?
User error. I mucked around with to_s on DateTime to use the time formats in strftime. When removed, the chained aggregate worked as anticipated.

Resources