Parent Object In Mongoid Embedded Relation Extensions - mongoid

Given a simple embedded relationship with an extension like this:
class D
include Mongoid::Document
embeds_many :es do
def m
#...
end
end
end
class E
include Mongoid::Document
embedded_in :d
end
You can say things like this:
d = D.find(id)
d.es.m
Inside the extension's m method, how do access the specific d that we're working with?

I'm answering this myself for future reference. If anyone has an official and documented way of doing this, please let me know.
After an hour or so of Googling and reading (and re-reading) the Mongoid documentation, I turned to the Mongoid source code. A bit of searching and guesswork lead me to #base and its accessor method base:
embeds_many :es do
def m
base
end
end
and then you can say this:
d = D.find(id)
d.es.m.id == id # true
base is documented but the documentation is only there because it is defined using attr_reader :base and documentation generated from attr_reader calls isn't terribly useful. base also works with has_many associations.
How did I figure this out? The documentation on extensions mentions #target in an example:
embeds_many :addresses do
#...
def chinese
#target.select { |address| address.country == "China"}
end
end
#target isn't what we're looking for, #target is the array of embedded documents itself but we want what that array is inside of. A bit of grepping about for #target led me to #base (and the corresponding attr_reader :base calls) and a quick experiment verified that base is what I was looking for.

Related

How to parse data and store it into variables using Nokogiri and Ruby

When I assign variable names such as service_names and name_array they are nil and nothing goes to the class variable ##product_names.
I used Pry to try the code without storing it into a variable and it works. It has the values I need.
I had this split up in more variables before to make cleaner code, for example:
require 'pry'
require 'rubygems'
require 'open-uri'
require 'nokogiri'
class KefotoScraper::CLI
##product_names =[]
PAGE_URL = "https://kefotos.mx/"
def call
binding.pry
puts "These are the services that Kefoto offers:"
#list_products
puts "which service would you like to select?"
#selection = gets.chomp
view_price_range
puts "Would you like to go back to the service menu? y/n"
answer = gets.chomp
if answer == "y"
call
end
end
private
def home_html
# #home_html ||=
# HTTParty.get root_path
Nokogiri::HTML(open(PAGE_URL))
end
#
# # TODO: read about ruby memoization
# def home_node
#
# #home_node ||=
# Nokogiri::HTML(PAGE_URL)
# end
def service_names
#service_names = home_html.css(".nav-link").map do
|link| link['href'].to_s.gsub(/.php/, "")
end
#service_names.each do |pr|
##product_names << pr
end
end
def list_products
i = 1
n = 0
while ##product_names.length < n
##product_names.each do |list_item|
puts "#{i} #{list_item[n]}"
i += 1
n += 1
end
end
end
def view_price_range
price_range = []
#service_links.each do |link|
if #service = link
link.css(".row").map {|price| price["p"].value}
price_range << p
end
price_range
end
def service_links
#service_links ||=
home_html.css(".nav-item").map { |link| link['href'] }
end
end
end
##product_names should contain the code that comes out of
home_html.css(".nav-link").map { |link| link['href'] }.to_s.gsub(/.php/, "")
which later I turn back to an array.
This is what it looks like in Pry:
9] pry(#<KefotoScraper::CLI>)> home_html.css(".nav-link").map { |link| link['href'] }.to_s.gsub(/.php/, "").split(",")
=> ["[\"foto-enmarcada\"", " \"impresion-fotografica\"", " \"photobooks\"", " \"impresion-directa-canvas\"", " \"impresion-acrilico\"", " \"fotoregalos\"]"]
[10] pry(#<KefotoScraper::CLI>)> home_html.css(".nav-link").map { |link| link['href'] }.to_s.gsub(/.php/, "").split(",")[0]
=> "[\"foto-enmarcada\""
Nokogiri's command-line IRB is your friend. Use nokogiri "https://kefotos.mx/" at the shell to start it up:
irb(main):006:0> #doc.css('.nav-link[href]').map { |l| l['href'].sub(/\.php$/, '') }
=> ["foto-enmarcada", "impresion-fotografica", "photobooks", "impresion-directa-canvas", "impresion-acrilico", "fotoregalos"]
That tells us it's not dynamic HTML and shows how I'd retrieve those values. Since an a tag doesn't have to contain href parameters I guarded against retrieving any such tags by accident.
You've got bugs, potential bugs and bad practices. Here are some untested but likely to work ways to fix them:
Running the code results in:
uninitialized constant KefotoScraper (NameError)
In your code you have #service and #service_links which are never initialized so...?
Don't do this because it's cruel:
def home_html
Nokogiri::HTML(open(PAGE_URL))
end
Every time you call home_html you (re)open and (re)read the page from the remote site and wasting your and their CPU and network time. Instead, cache the parsed document in a variable kind of like you did in your commented-out line using HTTParty. It's much more friendly to not hit sites repeatedly and helps avoid getting banned.
Moving on:
def service_names
#service_names = home_html.css(".nav-link").map do
|link| link['href'].to_s.gsub(/.php/, "")
end
#service_names.each do |pr|
##product_names << pr
end
end
I'd use something like get_product_names and return the array like I did in Nokogiri above:
def get_product_names
get_html.css('.nav-link[href]').map { |l|
l['href'].sub(/\.php$/, '')
}
end
:
:
##product_names = get_product_names()
Here's why I'd do it another way. You used:
link['href'].to_s.gsub(/.php/, "")
to_s is redundant because link['href'] is already returning a string. Stringizing a string wastes brain cycles when rereading/debugging the code. Be kind to yourself and don't do that.
require 'nokogiri'
html = '<a href="foo">'
doc = Nokogiri::HTML(html)
doc.at('a')['href'] # => "foo"
doc.at('a')['href'].class # => String
gsub Ew. How many occurrences of the target string do you anticipate to find and replace? If only one, which is extremely likely in a URL "href", instead use sub because it's more efficient; It only runs once and moves on whereas gsub looks through the string at least one additional time to see if it needs to run again.
/.php/ doesn't mean what you think it does, and it's a very subtle bug in waiting. /.php/ means "some character followed by "php", but you most likely meant "a period followed by 'php'". This was something I used to see all the time because other programmers I worked with didn't bother to figure out what they were doing, and being the senior guy it was my job to pick their code apart and find bugs. Instead you should use /\.php/ which removes the special meaning of ., resulting in your desired pattern which is not going to trigger if it encounters "aphp" or something similar. See "Metacharacters and Escapes" and the following section on that page for more information.
On top of the above, the pattern needs to be anchored to avoid wasting more CPU. /\.php/ will cause the regular expression engine to start at the beginning of the string and walk through it until it reaches the end. As strings get longer that process gets slower, and in production code that is processing GB of data it can slow down a system markedly. Instead, using an anchor like /\.php$/ or /\.php\z/ gives the engine a hint where it should start looking and can result in big speedups. I've got some answers on SO that go into this, and the included benchmarks show how they help. See "Anchors" for more information.
That should help you but I didn't try modifying your code to see if it did. When asking questions about bugs in your code we need the minimum code necessary to reproduce the problem. That lets us help you more quickly and efficiently. Please see "ask" and the linked pages and "mcve".

How to pass an array between Ruby files

I want to pass an array from a Ruby file to another one.
I have a three files:
main.rb
company.rb
applicant.rb
Here is the code for main.rb:
require './src/company.rb'
require './src/applicant.rb'
company = Company.new('data/boundless.json')
company.find_applicants('google')
Here is the code for company.rb:
require 'json'
require_relative 'applicant.rb'
class Company
attr_accessor :jobs , :arrOfApp
def self.load_json(filepath)
file = File.read(filepath)
return JSON.parse(file)
end
def initialize(filePath)
# Load the json file and loop over the jobs to create an array of instance of `Job`
# Assign the `jobs` instance variable.
jobs=Array.new
data_hash = Company.load_json(filePath)
numberOfJobs= data_hash['jobs'].length
for i in 0 ... numberOfJobs
jobs[i]=data_hash['jobs'][i]['applicants']
# puts jobs
end
end
## TODO: Impelement this method to return applicants from all jobs with a
## tag matching this keyword
def find_applicants(keyWord)
app =Applicant.new
arrOfApp=Array.new
app.data_of_applicant(jobs)
end
end
And finally the code for applicant.rb:
require_relative 'company.rb'
class Applicant
attr_accessor :id, :name, :tags
def initialize
end
def data_of_applicant(j)
id=Array.new
name=Array.new
tags=Array.new
puts j
end
end
The program reads a JSON file to get some information from it. Whenever I try to print the value being sent to the applicant file nothing is printed.
You can't pass an array from a ruby file to another one., you only can pass data between classes and objects.
Other possibilities which may help:
constants (Defined with starting capital letter)
global variables (starting with $)
Singletons
To keep data inside the class instances (objects) you need attributes (variables starting with #).
You can find this concepts in every beginner manual of ruby (and if not, then the manual is not worth to be used)
You made another common error.
Let's check it with a small example:
class Company
attr_accessor :jobs
def initialize()
jobs='This should be assigned to my accessor jobs'
end
end
puts Company.new.jobs
The result is an empty line.
What happend? In the initialize-method you define a local variable jobs. Local means, it is only available in the method ans is lost when the method leaves.
Correct would be 1) using the instance variable:
class Company
attr_accessor :jobs
def initialize()
#jobs='This should be assigned to my accessor jobs'
end
end
or 2) using the accessor method:
class Company
attr_accessor :jobs
def initialize()
self.jobs='This should be assigned to my accessor jobs'
end
end
In both cases the puts Company.new.jobs returns the text you defined.
See also Ruby instance variable access
if i'm reading this correctly, you're asking ruby to make the calculation, but never stating that it should be printed. i believe changing the last line of your main.rb to this:
puts company.find_applicants('google')
should suffice.

What is "current_account.people.find" in Rails strong parameter example?

I am new to Rails and am currently learning strong parameters in Rails 4 and following the below example from the official documentation:
`class PeopleController < ActionController::Base
# Using "Person.create(params[:person])" would raise an
# ActiveModel::ForbiddenAttributes exception because it'd
# be using mass assignment without an explicit permit step.
# This is the recommended form:
def create
Person.create(person_params)
end
# This will pass with flying colors as long as there's a person key in the
# parameters, otherwise it'll raise an ActionController::MissingParameter
# exception, which will get caught by ActionController::Base and turned
# into a 400 Bad Request reply.
def update
redirect_to current_account.people.find(params[:id]).tap { |person|
person.update!(person_params)
}
end
private
# Using a private method to encapsulate the permissible parameters is
# just a good pattern since you'll be able to reuse the same permit
# list between create and update. Also, you can specialize this method
# with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end`
Question 1:
What does current_account.people.find mean inside the update method?
Question 2:
Could someone please explain the person_params method. What is "params" inside the person_params method?
current_account is a most likely a private method that returns an Account instance. current_account.people.find(params[:id]) searches the people table for a person that belongs to the current_account and has an ID of params[:id]. Object#tap is a ruby method that yields a block with the current object, and then returns that object. In this case, the Person instance is updated inside the block and the returned from tap. Finally, redirect_to is a controller method that will redirect the request to a different path. redirect_to can take many different types of arguments, including an ActiveRecord model, a string, or a symbol. Passing it an ActiveRecord model will redirect the request to the model's resource path, which is defined in routes.rb. In this case, that path will most likely be /people/:id.
The params object is a hash containing parameter names and values. For example, the request /people?name=Joe&age=34 will result in the following params object: {name: 'Joe', age: '34'}.

Rails change default URL's

Im using rails for an app and I scaffolded the User database. I have a show.html.erb page for each user, but currently the URL's are /users/1 or /users/2, I want the URL's to be a random number, or the timestamp of creatation. So something like /users/201109123121 or even just a random;y generated number.
Is there a way to do this?
Yes, define to_param in your User model:
def to_param
timestamp
end
And define the timestamp attribute on your model and set it in a before_create:
class User < ActiveRecord::Base
before_create :set_timestamp
# model code goes here
def set_timestamp
self.timestamp = created_at.to_i
end
But if two users were created at precisely the same second, you will run into trouble. You may want to make this slightly more random than that. You will also now need to find them based on that field:
User.find_by_timestamp(params[:id])
Add this to your Gemfile
gem 'uuidtools'
Then do
#models/user.rb
class User < ActiveRecord::Base
before_create :set_uuid
# model code goes here
def set_uuid
self.uuid = UUIDTools::UUID.timestamp_create.to_s
end
end
Then you request it like so.
User.find_by_uuid(params[:id])
Unlike the above answer this should significantly reduce the likelihood of a race condition as that's the point of uuid (universal unique identifier)

Django Models - Conditionally adding an object to a ManyToManyField before saving

The following does not quite work, its purpose is:
Upon save, check for the existence of 'supervisor' in the 'operators', and add it too them if not.
class JobRecord(models.Model):
"""JobRecord model"""
project = models.ForeignKey(Project)
date = models.DateField()
supervisor = models.ForeignKey(User, related_name='supervisor_set')
operators = models.ManyToManyField(User, related_name='operators_set', help_text='Include the supervisor here also.')
vehicles = models.ManyToManyField(Vehicle, blank=True, null=True)
def __unicode__(self):
return u"%s - %s" % (self.project.name, self.date.strftime('%b %d'))
# --- over ride methods ---- #
def save(self, **kwargs):
# this must be done to get a pk
super(JobRecord, self).save(**kwargs)
# which makes this comparison possible
if self.supervisor not in self.operators.__dict__:
self.operators.add(self.supervisor)
# it seems to get this far ok, but alas, the second save attempt
# does not seem to work!
print self.operators.values()
super(JobRecord, self).save(**kwargs)
Thanks for your expertise, would be 'expert'!
You can do something like this to check if the supervisor is in the operators:
if self.operators.filter(id=self.supervisor.id).count() == 0:
And you don't need to save a second time after modifying the many to many field. (Many to many relations are stored in their own table.)
Ok, I've modified the to make the following. Actually, either conditional seems to do the trick. The issue now is that the add() method is not working for me.
#...
def save(self, **kwargs):
super(JobRecord, self).save(**kwargs)
if self.operators.filter(id=self.supervisor.id).count() == 0:
#if self.supervisor not in self.operators.values():
# either conditional will get to this point
self.operators.add(self.supervisor) # <-- this line doesn't save proper?
i have the same issue. if you are using a django form, do your check after the form is saved, and then add the many to many there. that was the only way i could get around it.

Resources