I figured that instead of leaving certain attributes in parent and embedded documents nil/null (e.g. total of an order if no price exists), I better not save them at all. How can I remove attributes that are nil before saving?
# embedded order position for each order
class Orderitem
include Mongoid::Document
field :quantity, :type => Integer
field :unit_price, :type => Integer
field :total, :type => Integer
field :economical_potential, :type => Integer
embedded_in :order
belongs_to :supplier
belongs_to :item
before_save :remove_empty_fields
private
def remove_empty_fields
attributes.each do |attr_name, value|
if value.nil?
# don't save attribute
end
end
end
end
Why do you want to remove attributes from your model? In that case, I would add another model called unit and add :price as an attribute. Then add a function to Orderitem called def total_of_unit which will return the total based on the number of units and their price.
In code it would look like this:
class Orderitem
...
field :quantity, :type => Integer
# drop :unit_price
# drop :total
field :economical_potential, :type => Integer
...
has_many :units
...
def total
#total = 0
self.units.each do |unit|
#total = #total + unit.price
end
return #total
end
end
Unit would look like this:
class Unit
field :unit_price, :type => Integer
belongs_to :Orderitem
end
Mongoid supports #unset, so you can use something like this:
order_item.unset(:total)
Related
I have a model Line Items embedded in Line model. In Line create view, I have provided ability to define multiple nested levels of line items.
Here is a random snap of param[:line]:
=> {"title"=>"Hello", "type"=>"World", "line_items"=>{"1"=>{"name"=>"A",
"position"=>"1", "children"=>{"1"=>{"name"=>"A1", "position"=>"1",
"children"=>{"1"=>{"name"=> "A11", "position"=>"1"}, "2"=>{"name"=>"A12",
"position"=>"2"}}}, "2"=>{"name"=>"A2", "position"=>"2"}}}, "3"=>
{"name"=>"B", "position"=>"3"}}}
In Line#create, I have:
def create
#line = Line.new(params[:line])
if #line.save
save_lines(params[:line][:line_items])
flash[:success] = "Line was successfully created."
redirect_to line_path
else
render :action => "new"
end
end
In Line#save_lines, I have:
# Save children up to fairly infinite nested levels.. as much as it takes!
def save_lines(parent)
unless parent.blank?
parent.each do |i, values|
new_root = #line.line_items.create(values)
unless new_root[:children].blank?
new_root[:children].each do |child|
save_lines(new_root.children.create(child))
end
end
end
end
end
LineItem Model looks like:
class LineItem
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Ancestry
has_ancestry
# Fields
field :name, type: String,
field :type, type: String
field :position, type: Integer
field :parent_id, type: Moped::BSON::ObjectId
attr_accessible :name, :type, :url, :position, :parent_id
# Associations
embedded_in :line, :inverse_of => :line_items
end
in Line model, i have:
# Associations
embeds_many :line_items, cascade_callbacks: true
Which work as expected. But, is there a better way to save the line_items recursively with Ancestry?
I think your code looks fine. I just refactored it.
How about:
def save_lines(parent)
parent.each do |i, values|
#get children hash if any
children = values.delete("children")
# create the object with whatever remain in values hash
#line.line_items.create(values)
# recurse if children isn't empty
save_lines(children) if children
end
end
I have a model User and Listing in my rails app. User has many listings and listing belongs to user. I also have a attribute name rating in user table. What I want is to search the keyword in Listing model and order it based on rating attribute of User model.
This is what I have in Listing model
searchable do
text :title, :default_boost => 3
text :description, :default_boost => 2
integer :category_id, :references => Category
integer :subcategory_id, :references => Subcategory
string :zipcode
time :created_at
double :user do
user.rating
end
end
And this is how I am trying to search
#search = Sunspot.search(Listing) do
keywords params[:q] do
fields :title
end
order_by THIS IS WHERE I NEED HELP
paginate :page => params[:page], :per_page => 20
end
You will need to add the keyword and rating attributes to the listing searachable method.
class Listing < ActiveRecord::Base
belongs_to :user
searchable do
text :keyword
integer :rating { user.rating }
end
end
Then in your search action in your controller
Listing.search do
fulltext params[:q]
order_by :rating, :desc
end
See http://sunspot.github.com/ for more examples.
Looking at your code, you need to change in your searchable method
double :user do
user.rating
end
to
double :rating do
user.rating
end
Im using Ruby on Rails to allow a user to add a project post much like Stackoverflow. I can do this with the regular MySQL database, but I am unsure as to how it works with Mongoid.
This is how the process works:
User writes some details about the project (client, date, description)
Add tags like Stackoverflow, where they just simply need to add a space between each one.
Submit the post
Now in my model I try to break the tags up into an array (splitting where there is a space) and then saving the tags one after the other. However, the row for the Project and the Tag do not reference one another. The Project tag_ids = [] and the Tag project_ids = []
project.rb model
class Project
include Mongoid::Document
include Mongoid::MultiParameterAttributes
field :client, :type => String
field :description, :type => String
field :url, :type => String
field :project_date, :type => Date
has_and_belongs_to_many :tags
attr_accessor :tag_names
after_save :assign_tags
def tag_names
#tag_names || tags.map(&:name).join(" ")
end
def assign_tags
#project = self
#project_id = self.id
if #tag_names
self.tag_names = #tag_names.split(/\s+/).map do |name|
Tag.find_or_create_by(:name => name)
end
end
end
end
tag.rb model
class Tag
include Mongoid::Document
field :name, :type=> String
has_and_belongs_to_many :projects
end
Any ideas as to how to add these reference ids? Thanks!
I think you need to do this:
t = Tag.find_or_create_by(:name => name)
self.tags << t unless (self.tags.include? t)
I am in the process of creating a dynamic database where user will be able to create resource type where he/she can add custom fields (multiple texts, strings, and files)
Each resource type will have the ability to display, import, export its data;
I've been thinking about it and here are my approaches. I would love to hear what do you guys think.
Ideas:
just hashing all the custom data in a data field (pro: writing is easier, con: reading back out may be harder);
children fields (the model will have multiple fields of strings, fields of text, and fields for file path);
fixed number of custom fields in the same table with a key mapping data hash stored in the same row;
Non-SQL approach, but then the problem would be generating/changing models on the fly to work with different custom fields;
Firstly you can create few models:
- StringData
- BooleanData
- TextData
- FileData
etc (all data and fields formats you need)
Each model will refferenced to some project, wich will contain information about fields
IE:
class Project < ActiveRecord::Base
has_many :project_fields
has_many :string_datas :through => project_fields
has_many :file_datas :through => project_fields
has_many :boolean_datas :through => project_fields
etc ...
end
class ProjectField < ActiveRecord::Base
# title:string field_type:string project_id:integer name:string
belongs_to :project
has_many :string_datas
has_many :file_datas
has_many :boolean_datas
etc ...
end
class StringData < ActiveRecord::Base
# data:string project_field_id:integer
belongs_to :project_field, :conditions => { :field_type => 'String' }
end
class FileData < ActiveRecord::Base
# data:file project_field_id:integer
belongs_to :project_field, :conditions => { :field_type => 'File' }
end
project = Project.new
project.project_fields.new(:title => "Product title", :field_type => "String", :name => 'product_title')
project.project_fields.new(:title => "Product photo", :field_type => "File", :name => 'product_photo')
project.save
<% form_for project do |f| -%>
<% project.project_fields.each do |field| -%>
<%= field_setter field %>
#=> field_setter is a helper method wich creates form element (text_field, text_area, file_field etc) for each type of prject_field
#=> ie: if field.field_type == 'String' it will return
#=> text_field_tag field.name => <input name='product_name' />
<% end -%>
<% end -%>
And create (update) method
def create
project = Project.new(params[:project])
project.project_fields.each do |field|
filed.set_field params[field.name]
# where set_field is model method for setting value depending on field type
end
project.save
end
It is not tested and optimized but it just showing the way you can implement it.
UPDATE: I've updated code but It's only model, you have to think yourself a little :) and you can try to find out another implementation
Why not just create a model for DynamicField?
Columns:
t.integer :dynamic_field_owner_id
t.string :dynamic_field_owner_type
t.string :name, :null => false
t.string :value
t.string :value_type_conversion, :default => 'to_s'
# any additional fields from paperclip, has_attachment, etc.
t.timestamps
model class:
class DynamicField > ActiveRecord::Base
belongs_to :dynamic_field_owner, :polymorphic => true
validates_presence_of :name
validates_inclusion_of :value_type_conversion, :in => %w(to_s to_i to_f)
validates :value_or_attachment
def value
read_attribute(:value).send(value_type_conversion)
end
private
def value_or_attachment
unless value? || file?
errors.add_to_base('Must have either value or file')
end
end
end
I've not seen this feature as a plug in and was wondering how to achieve it, hopefully using rails.
The feature I'm after is the ability to rate one object (a film) by various attributes such as plot, entertainment, originality etc etc on one page/form.
Can anyone help?
I don't think you need a plugin to do just that... you could do the following with AR
class Film < ActiveRecord::Base
has_many :ratings, :as => :rateable
def rating_for(field)
ratings.find_by_name(field).value
end
def rating_for=(field, value)
rating = nil
begin
rating = ratigns.find_by_name(field)
if rating.nil?
rating = Rating.create!(:rateable => self, :name => field, :value => value)
else
rating.update_attributes!(:value => value)
end
rescue ActiveRecord::RecordInvalid
self.errors.add_to_base(rating.errors.full_messages.join("\n"))
end
end
end
class Rating < ActiveRecord::Base
# Has the following field:
# column :rateable_id, Integer
# column :name, String
# column :value, Integer
belongs_to :rateable, :polymorphic => true
validates_inclusion_of :value, :in => (1..5)
validates_uniqueness_of :name, :scope => :rateable_id
end
Of course, with this approach you would have a replication in the Rating name, something that is not that bad (normalize tables just for one field doesn't cut it).
You can also use a plugin ajaxfull-rating
Here's another, possibly more robust rating plugin...it's been around for a while and has been revised to work with Rails 2
http://agilewebdevelopment.com/plugins/acts_as_rateable