I'm building a site on Refinery CMS, and have generated two extensions: one for Brands, and another for Bicycle Types (it's a site for a bike shop).
Now, what I want to do is have the Brands extension handle the creation of brand pages, which will be pulled into a brand index. On this page, I want to be able to filter by Bicycle Type, which is where the second extension comes in. Through the Bicycle Type extension, you can create a bicycle type, which I want to associate to a Brand. A Brand can have multiple Bicycle Types, and vice versa.
So, I edited the Brands model to add has_and_belongs_to_many :bicycle_types, and the Bicycle Types model to include has_and_belongs_to_many :brands and accepts_nested_attributes_for :brands. I wrote a migration to create a join table, and everything was going well so far.
I then went to modify the form for the Brands extension, and got my checkboxes displaying correctly and seemingly generating the right code. However, the problem occurs when I come to submit the form - I get NameError in Refinery::Brands::Admin::BrandsController#update and uninitialized constant Refinery::Brands::Brand::BicycleType.
The parameters I get look like the bicycle type IDs are being passed through correctly:
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"3193ZMPXkmHdgZThXwAurD6xF2eZ533Tb71pAi7Jxbs=",
"switch_locale"=>"en",
"brand"=>{"title"=>"Cannondale",
"teaser"=>"",
"splash"=>"",
"details"=>"",
"introduction"=>"",
"blockquote"=>"",
"bicycle_type_ids"=>["1",
"2"],
"logo_id"=>"",
"teaser_image_id"=>"",
"splash_image_id"=>""},
"id"=>"2",
"locale"=>:en}
I've been trying to figure this out and just keep hitting the same brick wall, so any help would be greatly appreciated!
Here's my code. Let me know if anything else would help.
Brands Controller
module Refinery
module Brands
module Admin
class BrandsController < ::Refinery::AdminController
crudify :'refinery/brands/brand',
:xhr_paging => true
end
end
end
end
Brands Model
module Refinery
module Brands
class Brand < Refinery::Core::BaseModel
self.table_name = 'refinery_brands'
attr_accessible :title, :teaser, :splash, :details, :introduction, :blockquote, :logo_id, :teaser_image_id, :splash_image_id, :position, :bicycle_type_ids
translates :title, :teaser, :splash, :details, :introduction, :blockquote
class Translation
attr_accessible :locale
end
validates :title, :presence => true, :uniqueness => true
belongs_to :logo, :class_name => '::Refinery::Image'
belongs_to :teaser_image, :class_name => '::Refinery::Image'
belongs_to :splash_image, :class_name => '::Refinery::Image'
has_and_belongs_to_many :bicycle_types
end
end
end
Bicycle Types Model
module Refinery
module BicycleTypes
class BicycleType < Refinery::Core::BaseModel
self.table_name = 'refinery_bicycle_types'
attr_accessible :title, :position
translates :title
class Translation
attr_accessible :locale
end
validates :title, :presence => true, :uniqueness => true
has_and_belongs_to_many :brands
accepts_nested_attributes_for :brands
end
end
end
Migration
class AddRefineryBicycleTypesBrands < ActiveRecord::Migration
def change
create_table :bicycle_types_brands, :id => false do |t|
t.references :bicycle_type
t.references :brand
end
add_index :bicycle_types_brands, [:bicycle_type_id, :brand_id], :unique => true
end
end
Form Partial (at least the part where I'm building my checkboxes)
<div class="field">
<%= f.label :bicycle_types %>
<% Refinery::BicycleTypes::BicycleType.order(:title).each do |bicycle_type| %>
<label class="checkbox">
<%= check_box_tag "#{f.object_name}[bicycle_type_ids][]", bicycle_type.id, f.object.bicycle_types %>
<%= bicycle_type.title %>
</label>
<% end %>
</div>
If the rest of the partial would be useful, or anything else for that matter, please let me know. Any help would be greatly appreciated!
you must specify the full class name:
has_and_belongs_to_many :bicycle_types, :class_name => "Refinery::BicycleTypes::BicycleType"
This is not your case but if you want to call the join table in a 'refinery style' (i.e refinery_bicycle_types_brands), you must also declare the join table:
has_and_belongs_to_many :bicycle_types, :join_table => :refinery_bicycle_types_brands, :class_name => "Refinery::BicycleTypes::BicycleType"
Bye
Related
I'm trying to obtain a subset of records for a grouped_collection_select, in the schema I have a Brand and a Model, and we handle different lines of products, when we request a product, we specify the line that we are going to request, so, we can have the Brands, and the models of that line, so the models are defined as follows:
Model for Brand
class Brand < ActiveRecord::Base
validates :brand_key, uniqueness: true, presence:true
has_many :models
accepts_nested_attributes_for :models, allow_destroy: true
end
Model for Modelx
class Modelx < ActiveRecord::Base
belongs_to :brand
belongs_to :line
validates :model_key, uniqueness: true, presence:true
end
Model for Line
class Line < ActiveRecord::Base
validates :line_key, uniqueness: true, presence:true, length: {in: 1..6}
has_many :line_features, -> { order(:sort_order) }
has_many :modelx
accepts_nested_attributes_for :line_features, allow_destroy: true
end
Model for Product
class Product < ActiveRecord::Base
belongs_to :vendor
belongs_to :line
belongs_to :brand
belongs_to :modelx
belongs_to :operating_status
has_many :line_features, -> { order(:sort_order)}
store_accessor :line_features
end
Controller for Product (new)
def new
#brands=brand.where(id: Modelx.select(:brand_id).where(line_id: params[:line_id]))
#modelxs=Modelx.where(line_id: params[:line_id])
...
end
excerpt from partial form
<%= f.collection_select( :brand_id, #brands, :id, :brand,{:prompt =>'Brands'}) %>
<%= f.grouped_collection_select(:modelx_id, #brands, :modelx, :brand, :id, :modelox) %>
Now, the problem that I'm facing is that when I display a model I need to present only the models available for that brand and line, but, the request is bringing all the models for the brand as it's supposed to be and I don't know how to discriminate those lines that are not needed.
Any help, hint or suggestion is highly appreciated.
Update to question
I don't know if this is a workaround or a solution to the question, but, it was the only way that I found to get to a solution for the requirement, for that reason I'm posting as an update instead of answering the question for whom it may help.
Reviewed the documentation once again and find out that the method :modelx referred by <%= f.grouped_collection_select(:modelx_id, #brands, :modelx, :brand, :id, :modelox) %> was requesting all the models as noted in apidock, this method was the solution for the problem.
Created a method in the brand model according to the subset depicted above, due to the fact that the grouping was made by brand, here the excerpt
Model for Brand (Excerpt)
...
def modelx_ltvi
Modelx.where("line_id = ? and brand_id =?",$line_id, self.id)
end
Special Note: Due to my inexperience, I was not able to pass the value of the :line_id from the form, so, I put it in a global variable.
Modified the form partial
Excerpt from partial form
...
<% $line_id=#product.line_id %>
...
<%= f.grouped_collection_select(:modelx_id,
#brands, :modelx_ltvi, :brand,
:id, :modelx) %>
<% end %>
And that makes the hamster run.
Filter your models by the brands you just found, something like:
#brands=Brand.where(id: Modelx.select(:brand_id).where(line_id: params[:line_id]))
#modelxs=Modelx.where(line_id: params[:line_id], brand_id: #brands.map(&:id))
#selected_model_names = #modelxs.map(&:name).sort.uniq
I am working on a self-learning Rails application (the source code can be found here. I want to validate the presence of the content before posting a text or an image:
.
Those are my models or look below:
class Post < ActiveRecord::Base
belongs_to :user
default_scope { order ("created_at DESC")}
belongs_to :content, polymorphic: true
has_reputation :votes, source: :user, aggregated_by: :sum
end
class PhotoPost < ActiveRecord::Base
has_attached_file :image, styles: {
post: "200x200>"
}
end
class TextPost < ActiveRecord::Base
attr_accessible :body
end
Here are my controllers in case they have a relation with this. Any other files can be found in my Github account. I am sure it will be messy to copy the whole project (that is why I am giving links for the controllers and for my project).
So what I have tried so far. (I tried those on the Posts Model)
=> Using validates_associated
validates_associated :content, :text_post
and getting an error "undefined method `text_post' for #Post:0x517c848>"
=> Used validates
validates :content, :presence => true
and getting no error however a post is created with no text.
validates :body, :presence => true
and getting an error "undefined method `body' for #Post:0x513e4a8>"
If you need any other information please let me know and I will provide it asap.
Thank you.
It would seem you have quite a confusing model setup with some key missing relation rules. E.g. Polymorphic rule which is not being utilised and a has_many relation between User and Post with no sign a of a user_id value in the Post model. Here is how I would set it up:
User.rb
def User << ActiveRecord::Base
has_many :text_posts
has_many :photo_posts
end
TextPost.rb
def TextPost << ActiveRecord::Base
attr_accessible :body, :user_id
belongs_to :user
validates :body, :presence => true
end
PhotoPost.rb
def PhotoPost << ActiveRecord::Base
attr_accessible :image, :user_id
belongs_to :user
validates :file, :presence => true, :format => {
:with => %r{\.(gif|png|jpg)$}i,
:message => "must be a URL for GIF, JPG or PNG image."
}
end
Then in your view you would need to do:
<%= form_for #text_post do |f| %>
# ...
<% end %>
And in your controller you can modify the create method to include the current_user from devise and assign it to the new text post record (user_id attribute):
text_posts_controller.rb
def create
#text_post = current_user.text_posts.new(params[:text_post])
end
This adheres more to the DRY principle which Ruby on Rails excels at - you shouldn't be writing alot of code to just create a new record.
I would advise on reading up on some Ruby on Rails standard and best practises. You shouldn't need to create a method in the Dashboard Model in order to create a new TextPost or PhotoPost record. This is a very confusing way of going about it; instead you should be utilising the power of ActiveRecord relation.
I would advise checking out Railscasts. They have alot of fulfilling content.
I have a database of skills that relate to each other as prerequisites to each other. In an index of skills, I'd like to be able to search through other skills and add 1 or more as prerequisites. It's important to note that I ONLY want the user to be able to add prerequisites, not remove them, as that's taken care of through an up-down voting system. I'm using JQuery Tokeninput and actually have all of this working except for one thing: I can't figure out how to only add prerequisites, rather than replacing all the prerequisites for a particular skill on submit.
Models:
class Skill < ActiveRecord::Base
attr_accessible :skill_relationship_attributes, :prereq_tokens
has_many :skill_relationships
has_many :prereqs, :through => :skill_relationships
has_many :inverse_skill_relationships, :class_name => 'SkillRelationship', :foreign_key => "prereq_id"
has_many :inverse_prereqs, :through => :inverse_skill_relationships, :source => :skill
attr_reader :prereq_tokens
accepts_nested_attributes_for :skill_relationships, :allow_destroy => true
def prereq_tokens=(ids)
self.prereq_ids = ids.split(",")
end
end
class SkillRelationship < ActiveRecord::Base
attr_accessible :skill_id, :prereq_id, :skill_attributes, :prereq_attributes
belongs_to :skill
belongs_to :prereq, :class_name => 'Skill'
end
JQuery:
$('#skill_prereq_tokens').tokenInput('/skills.json',
{ theme:'facebook',
propertyToSearch:'title',
queryParam:'search',
preventDuplicates:'true'
});
View:
<%= simple_form_for skill do |f| %>
<%= f.input :prereq_tokens %>
<%= f.submit %>
<% end %>
I feel a bit silly for not getting this before, but I solved my problem by changing how prereq_tokens became prereq_ids in my Skill model.
I just changed this:
def prereq_tokens=(ids)
self.prereq_ids = ids.split(",")
end
to this:
def prereq_tokens=(ids)
self.prereq_ids += ids.split(",")
end
That's it. That little plus sign before the equals sign. I hope this helps anyone else who codes too long without a break!
Here are my models:
class Lesson < ActiveRecord::Base
belongs_to :topic, :polymorphic => true
validates_presence_of :topic_type, :topic_id
end
class Subject < ActiveRecord::Base
has_many :lessons, :as => :topic
end
class Category < ActiveRecord::Base
has_many :lessons, :as => :topic
end
Now, what I need is a form that will allow the user to create or update Lessons. The questions is, how can I provide a select menu that offers a mix of Subjects and Categories? (To the user, on this particular form, Subjects and Categories are interchangeable, but that's not the case elsewhere.)
Ideally, this would look something like this:
views/lessons/_form.html.haml
= simple_form_for(#lesson) do |f|
= f.input :title
= f.association :topic, :collection => (#subjects + #categories)
That won't work because we'd only be specifying the topic_id, and we need the topic_types as well. But how can we specify those values?
I guess the crux of the problem is that I really want a single select menu that specifies two values corresponding to two different attributes (topic_id and topic_type). Is there any elegant railsy way to do this?
A few notes:
a) Single table inheritance would make this issue go away, but I'd like to avoid this, as Categories and Subjects have their own relationship… I'll spare you the details.
b) I might could pull some javascript shenanigans, yes? But that sounds messy, and if there's a cleaner way to do it, some magic form helper or something, then that's certainly preferable.
c) Though I'm using simple_form, I'm not wedded to it, in case that's complicating matters.
Thanks
If you don't wish to use STI, you can do something similar: create a new model Topic(name:string) which will polymorphically reference Subject or Category.
class Lesson < ActiveRecord::Base
belongs_to :topic
validates_presence_of :topic_id
end
class Topic < ActiveRecord::Base
belongs_to :topicable, :polymorphic => true
end
class Subject < ActiveRecord::Base
has_one :topic, :as => :topicable
has_many :lessons, :through => :topic
accepts_nested_attributes_for :topic
end
class Category < ActiveRecord::Base
has_one :topic, :as => :topicable
has_many :lessons, :through => :topic
accepts_nested_attributes_for :topic
end
In the view where you create a new Subject/Category:
<%= form_for #subject do |subject_form| %>
<%= subject_form.fields_for :topic do |topic_fields| %>
<%= topic_fields.text_field :name %>
<% end %>
<% end %>
After thinking this through, the less dirty implementation IMO would be to hire the JS shenanigans (b):
= f.input_field :topic_type, as: :hidden, class: 'topic_type'
- (#subjects + #categories).each do |topic|
= f.radio_button :topic_id, topic.id, {:'data-type' => topic.class.name, class: 'topic_id'}
With a sprinkle of JS (your needs may vary):
$('input:radio.topic_id').change(function() {
$('input:hidden.topic_type').val($(this).attr('data-type'));
});
Notes:
I use a radio button to select a topic (category or subject) in a list
The class name of each of possible topic is stored in an attribute 'data-type'
When a radio button is selected, the class name is copied to the hidden input via JS
Using: HTML5, jQuery, haml, simple_form
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